Wscats/articles

webpack入门

Wscats opened this issue · 0 comments

安装 webpack

//全局安装
npm install -g webpack
//安装到你的项目目录
npm install --save-dev webpack

这里记得一定要全局安装 webpack,不然无法在命令行执行webpack指令

准备阶段

npm init

执行后会在根目录生成一个package.json的文件
文件目录结构如下:
image

// Greeter.js
module.exports = function () {
  var greet = document.createElement("div");
  greet.textContent = "Hi there and greetings!";
  console.log("test");
  return greet;
};
//main.js
var greeter = require("./Greeter.js");
document.getElementById("root").appendChild(greeter());
<!--index.html-->
<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="utf-8">
		<title>Webpack Sample Project</title>
	</head>
	<body>
		<div id='root'>
		</div>
		<script src="bundle.js"></script>
	</body>
</html>
//webpack.config.js
module.exports = {
  //devtool: 'eval-source-map', //用于调试代码
  entry: __dirname + "/app/main.js", //已多次提及的唯一入口文件
  output: {
    path: __dirname + "/public", //打包后的文件存放的地方
    filename: "bundle.js", //打包后输出文件的文件名
  },
};

3.执行命令打包

当我们在根目录下有上面写好完整的webpack.config.js之后,我们可以在命令行执行webpack执行打包,如果成功就会在 public 文件夹生成一个 bundle.js 文件
image

当然你还可以在 package.json 自己定义好一个执行命令来代替这些繁琐的命令,例如

//package.json
{
  "name": "wscat-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "dependencies": {
    "webpack": "^1.14.0"
  },
  "devDependencies": {},
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "webpack"
  },
  "author": "",
  "license": "ISC"
}

加上这句之后

"start": "webpack" //配置的地方就是这里啦,相当于把npm的start命令指向webpack命令

你就可以执行npn run start

image

还有注意的是,如果不加下面的代码,在调试代码的时候很可能就找不到行数了

devtool: 'eval-source-map', //用于调试代码

加了之后就会显示未合并之前的文件所在行数
image
未加则显示合并后在 bundle.js 的行数
image

devtool 选项对应的配置结果

  1. source-map:在一个单独的文件中产生一个完整且功能完全的文件。这个文件具有最好的 source map,但是它会减慢打包文件的构建速度
  2. cheap-module-source-map:在一个单独的文件中生成一个不带列映射的 map,不带列映射提高项目构建速度,但是也使得浏览器开发者工具只能对应到具体的行,不能对应到具体的列(符号),会对调试造成不便
  3. eval-source-map:使用 eval 打包源文件模块,在同一个文件中生成干净的完整的 source map。这个选项可以在不影响构建速度的前提下生成完整的 sourcemap,但是对打包后输出的 JS 文件的执行具有性能和安全的隐患。不过在开发阶段这是一个非常好的选项,但是在生产阶段一定不要用这个选项
  4. cheap-module-eval-source-map:这是在打包文件时最快的生成 source map 的方法,生成的 Source Map 会和打包后的 JavaScript 文件同行显示,没有列映射,和 eval-source-map 选项具有相似的缺点

上述选项由上到下打包速度越来越快,不过同时也具有越来越多的负面作用,较快的构建速度的后果就是对打包后的文件的的执行有一定影响

配置服务器

npm install -g webpack-dev-server
npm install --save-dev webpack-dev-server

webpack.config.js文件添加配置项

module.exports = {
  devtool: "eval-source-map", //用于调试代码
  entry: __dirname + "/app/main.js", //已多次提及的唯一入口文件
  output: {
    path: __dirname + "/public", //打包后的文件存放的地方
    filename: "bundle.js", //打包后输出文件的文件名
  },
  devServer: {
    contentBase: "./public", //本地服务器所加载的页面所在的目录
    historyApiFallback: true, //不跳转
    //port 设置默认监听端口,如果省略,默认为”8080“
    inline: true, //实时刷新
  },
};

然后在命令行执行webpack-dev-server
image
在浏览器打开http://localhost:8080/
就会显示静态页面,如果改动文件,浏览器还会热更新

devserver 配置选项和功能描述

  1. contentBase:默认 webpack-dev-server 会为根文件夹提供本地服务器,如果想为另外一个目录下的文件提供本地服务器,应该在这里设置其所在目录(本例设置到“public"目录)
  2. port:设置默认监听端口,如果省略,默认为”8080“
  3. inline:设置为 true,当源文件改变时会自动刷新页面
  4. colors:设置为 true,使终端输出的文件为彩色的
  5. historyApiFallback:在开发单页应用时非常有用,它依赖于 HTML5 history API,如果设置为 true,所有的跳转将指向 index.html

loader

css-loader

webpack 提供两个工具处理样式表,css-loader 和 style-loader,二者处理的任务不同
css-loader使你能够使用类似@import 和 url(...)的方法实现 require()的功能
style-loader将所有的计算后的样式加入页面中,二者组合在一起使你能够把样式表嵌入 webpack 打包后的 JS 文件中

npm install --save-dev style-loader css-loader

然后我们就可以在webpack.config.js文件里面添加这个 loader 的配置项

注意的是style-loader必须在css-loader之前引入

module.exports = {
  devtool: "eval-source-map", //用于调试代码
  entry: __dirname + "/app/main.js", //已多次提及的唯一入口文件
  output: {
    path: __dirname + "/public", //打包后的文件存放的地方
    filename: "bundle.js", //打包后输出文件的文件名
  },
  module: {
    loaders: [
      {
        test: /\.css$/,
        loader: "style-loader!css-loader",
      },
    ],
  },
  devServer: {
    contentBase: "./public", //本地服务器所加载的页面所在的目录
    historyApiFallback: true, //不跳转
    inline: true, //实时刷新
  },
};

"-loader"其实是可以省略不写的,多个 loader 之间用“!”连接起来

/*main.css*/
p{
	color: red;
}
<!--index.html-->
<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="utf-8">
		<title>Webpack Sample Project</title>
	</head>
	<body>
		<div id='root'>
		</div>
		<p>hello</p>
		<script src="bundle.js"></script>
	</body>
</html>

此时我们就可以在 main.js 里面直接引进main.css,那所有样式都会打包成 js,在项目运行的时候会转化为**<style>**标签自动引进原页面中

webpack 只有单一的入口,其它的模块需要通过import,require,url等导入相关位置,为了让 webpack 能找到”main.css“文件,我们把它导入”main.js “中

//main.js
var greeter = require("./Greeter.js");
require("./main.css");
document.getElementById("root").appendChild(greeter());

url-loader

还可以对图片进行打包,用这个还要先安装

module.exports = {
  devtool: "eval-source-map", //用于调试代码
  entry: __dirname + "/app/main.js", //已多次提及的唯一入口文件
  output: {
    path: __dirname + "/public", //打包后的文件存放的地方
    filename: "bundle.js", //打包后输出文件的文件名
  },
  module: {
    loaders: [
      {
        test: /\.css$/,
        loader: "style-loader!css-loader",
      },
      {
        test: /\.(png|jpg)$/,
        loader: "url-loader?limit=8192",
      },
    ],
  },
  devServer: {
    contentBase: "./public", //本地服务器所加载的页面所在的目录
    historyApiFallback: true, //不跳转
    inline: true, //实时刷新
  },
};

配置信息的参数“?limit=8192”表示将所有小于 8kb 的图片都转为 base64 形式(其实应该说超过 8kb 的才使用 url-loader 来映射到文件,否则转为 data url 形式)

//main.js
var img1 = document.createElement("img");
img1.src = require("./logo.png");
document.body.appendChild(img1);

babel-loader

webpack 中 babel-loader 转译 ES2015
安装babel-loader,现在我们就可以放心的用 ES6 的语法了

npm install --save-dev babel-loader
npm install --save-dev babel-core babel-preset-es2015 //安装babel  实现 ES6 到 ES5

在 webpack.config.js 中添加配置项

module: {
		loaders: [ {
			test: /\.js$/,
			exclude: /node_modules/,
			loader: "babel-loader",
		}]
},
//ES6转ES5
babel: {
		presets: ['es2015']
},
// Greeter.js
module.exports = function () {
  var greet = document.createElement("div");
  greet.textContent = "Hi there and greetings!";
  console.log("test2");
  return greet;
};

可以把上面的代码用 ES6 改写成下面的格式

// Greeter.js
export default function f() {
  var greet = document.createElement("div");
  greet.textContent = "Hi there and greetings!";
  console.log("test2");
  return greet;
}
var greeter = require("./Greeter.js"); //ES5
import greeter from "./Greeter.js"; //ES6

exportexport default均可用于导出常量、函数、文件、模块等,你可以在其它文件或模块中通过 import+(常量 | 函数 | 文件 | 模块)名的方式,将其导入,以便能够对其进行使用,但在一个文件或模块中,export、import 可以有多个,export default 仅有一个

所以上面是只导出一个函数的情况,如果导出多个可以用下面这种方式

// test.js
export const str = "hello world";
export function f(a) {
  return a + 1;
}
import { str, f } from "./test.js";

react 下的 babel-loader

在 react 下需要以下几个重要的 babel 文件

模块 作用
babel-core babel-core 的作用是把 js 代码分析成 ast,方便各个插件分析语法进行相应的处理,比如有些新语法在低版本 js 中是不存在的,如箭头函数,rest 参数,函数默认值等,这种语言层面的不兼容只能通过将代码转为 ast,分析其语法后再转为低版本 js
babel-loader
babel-preset-env/babel-preset-2015/babel-preset-latest 现代的浏览器大多支持 ES6 的 generator,但是如果你使用 babel-preset-es2015,它会将 generator 函数编译为复杂的 ES5 代码,这是没有必要的。但使用 babel-preset-env,我们可以声明环境,然后该 preset 就会只编译包含我们所声明环境缺少的特性的代码,因此也是比较推荐的方式
babel-preset-react jsx 是需要编译才能被浏览器识别的,它就是被 Babel 编译的,具体说来是被 babel-preset-react 来编译的

.babelrc文件需要添加react

{
  "presets": ["react", "env"]
}

webpack.config.js下的配置可以写成这样

module: {
  rules: [
    {
      test: /\.js[x]?$/,
      exclude: /(node_modules)/,
      use: {
        loader: "babel-loader",
      },
    },
  ];
}

或者不写.babelrc 文件直接在 webpack.config.js 下这样配置,就是等同于把.babelrc 下的配置换成在 query 参数中配置

module: {
  rules: [
    {
      test: /\.js[x]?$/,
      exclude: /(node_modules)/,
      use: {
        loader: "babel-loader",
        query: {
          presets: ["env", "react"],
        },
      },
    },
  ];
}

vue-loader

vue-loader 能够把我们的组件.vue 文件转化为 js 加载到我们项目中

<!-- app.vue-->
<template>
	<div>{{msg}}</div>
</template>
<script>
	export default {
		data() {
			return {
				msg: 'Hello from vue-loader!'
			}
		},
	}
</script>
<style>
	div {
		color: red;
	}
</style>

这里面要注意的是 vue2 开始要用render: (createElement) => createElement(App)来注册组件,components 在 vue1 才能使用了,当然在组件.vue 文件里面注册组件还是可以用 components,
还有需要注意的是 vue2 之后不能在 el 为 body 和 html 节点上添加组件,所以我这里改成el: '#app'

var Vue = require("vue");
var App = require("./app.vue");
//import App from './app.vue'
new Vue({
  el: "#app",
  render: (createElement) => createElement(App),
  /*components: {
		app: App
	}*/
});

webpack.config.js 要添加 resolve 和 vue-loader

//webpack.config.js
module.exports = {
  devtool: "eval-source-map", //用于调试代码
  entry: __dirname + "/app/abc.js", //已多次提及的唯一入口文件
  output: {
    path: __dirname + "/public", //打包后的文件存放的地方
    filename: "bundle.js", //打包后输出文件的文件名
  },
  module: {
    loaders: [
      {
        test: /\.css$/,
        loader: "style-loader!css-loader",
      },
      {
        test: /\.(png|jpg)$/,
        loader: "url-loader?limit=8192",
      },
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: "babel-loader",
      },
      {
        test: /\.vue$/,
        loader: "vue-loader",
      },
    ],
  },
  devServer: {
    contentBase: "./public", //本地服务器所加载的页面所在的目录
    historyApiFallback: true, //不跳转
    inline: true, //实时刷新
  },
  resolve: {
    alias: {
      vue: "vue/dist/vue.js",
    },
  },
};

file-loader

webpack 加载 css 文件中的 eot,ttf 等格式
可以用来处理 ttf 格式等文件
需要先安装 file-loader

npm install file-loader

然后配置加载器

{
	test: /\.eot/,
	loader: 'file-loader?prefix=font/'
}, {
	test: /\.woff/,
	loader: 'file-loader?prefix=font/&limit=10000&mimetype=application/font-woff'
}, {
	test: /\.ttf/,
	loader: 'file-loader?prefix=font/'
}, {
	test: /\.svg/,
	loader: 'file-loader?prefix=font/'
}

sass-loader

安装两个模块
npm install sass-loader node-sass

编写 sass 格式的文件

<style scope lang="scss">
           $size: 20px;
           p{
                font-size: $size;
              }
</style>

配置 webpack.config.js 文件

{
	test: /\.scss/,
	loader: 'sass-loader'
}

less-loader

npm install less,less-loader --save-dev

webpack.config.js修改:

module: {
  loaders: [{ test: /\.less$/, loader: "style-loader!css-loader!less-loader" }];
}

在 module 的 loaders 中,增加了!less-loader
如此便可以在 js 中,require .less 文件

html-webpack-plugin

  • 自动为 html 文件中引入的外部资源如script、link动态添加每次编译后的哈希值,防止引用缓存的外部文件问题
  • 自动生成创建 html 入口文件,比如单页面可以生成一个 html 文件入口,配置 N 个html-webpack-plugin可以生成 N 个页面入口
module.exports = {
  //code...
  output: {
    path: __dirname + "/dist",
    //注意这里输出的是[name],配合后面的hash: true设置
    filename: "[name].js",
  },
  plugins: [
    //自动生成index.html
    new HtmlWebpackPlugin({
      //可以接受一个模板,支持ejs和jade等
      //template: 'src/index.html',
      hash: true,
    }),
  ],
};

uglify-js-plugin

webpack 自带了压缩 JS 的插件,配置如下

var webpack = require("webpack");
// 插件
plugins: [
  // 压缩JS文件
  // new webpack.optimize.UglifyJsPlugin(),
  new webpack.optimize.UglifyJsPlugin({
    compress: {
      warnings: false,
    },
    sourceMap: true,
  }),
];

DefinePlugin

有时候我们想在 webpack 全局里面定义一个变量,比如用来区分生产环境开发环境,公共参数和版本号等,我们就可以通过 DefinePlugin 来实现

new webpack.DefinePlugin({
  __DEV__: true,
});

然后在我们代码里面就可以

__DEV__ ? require("A.js") : require("B.js");

其他问题

打包出现以下问题,那是因为一些第三方包用 ES6 语法,在压缩的时候出问题了

Unexpected token: punc (()

解决问题很简单,在根目录下新建.babelrc文件进行以下配置,看 webpack 的情况而定,可以配置转 ES5

{
  "presets": ["env"]
   //"presets": ["es2015"]//看babel环境进行配置
}