一、简介

Webpack是一个JavaScript应用程序静态模块打包器;当webpack处理应用程序时,它会在内部构建一个依赖关系图来映射项目的依赖并生成一个或多个包(bundles)。

webpack还可以通过加载器支持用各种语言和预处理器编写的模块,例如:CoffeeScript、Less等。

二、安装

运行以下命令安装webpack(需要安装NodeJs):

npm install webpack --save-dev

三、配置

1、入口(Entry)

入口点指示webpack应该从哪个模块开始构建程序的依赖关系图,webpack会计算出入口点依赖的其他模块和库。

入口点的默认值为./src/index.js,可以通过在webpack的配置中配置entry属性来指定。

  • webpack.config.js
module.exports = {
	entry: './myentry.js'
};

2、输出(Output)

output属性指示webpack在哪里创建打包后的文件。对于主(main)输出文件,默认值为./dist/main.js;对于生成的其他文件,默认值为./dist目录;可以通过在webpack的配置中配置output属性来指定。

  • webpack.config.js
const path = require('path');
module.exports = {
	entry: './myentry.js'
	output: {
		path: path.resolve(__dirname, 'dist'),
		filename: 'my-bundle.js'
	}
};

3、加载器(Loaders)

默认情况下webpack只能处理JavaScript和JSON文件;使用加载器可以让webpack处理其他类型的文件,将它们转换为可以被应用程序使用的有效模块,并将它们添加到依赖关系图中。在配置文件中通过以下属性配置:

test: 定义哪些文件需要被转换

loader: 表示对于test属性指定的文件,使用哪个加载器来转换

  • webpack.config.js
module.exports = {
	entry: './myentry.js',
	module: {
		loaders: [
			{
				test: /\.css$/,
				loader: 'style!css' //'style-loader!css-loader'
			}	
		]
	}
};

4、插件(Plugins)

加载器被用来转换某些类型的模块,插件可以执行更广泛的任务,例如:打包优化、资源管理和环境变量注入。

使用插件前,需要先引入插件(require())并将它添加到插件数组中;大部分插件都可以通过选项进行自定义。如果需要在配置中多次使用同一个插件实现不同目的,需要通过new操作符来创建它的实例。在配置文件中通过plugins属性配置:

  • webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin'); 

module.exports = {
	entry: './myentry.js',
	output: {
		filename: 'my-bundle.js'
	},
	plugins: [
		new HtmlWebpackPlugin({
			title: 'Webpack-Demo',
			filename: './demo/index.html'
		})
	]
};

5、模式(Mode)

通过配置mode属性为developmentproduction、或none可以启用与每个环境对应的webpack内置优化,默认值为production。

  • webpack.config.js
module.exports = {
  mode: 'production'
};

四、运行

使用以下命令运行webpack打包:

  • 方式一

默认会从当前路径下找webpack.config.js配置文件:

webpack
  • 方式二

使用--config参数指定配置文件:

webpack --config webpack.config.js
  • 方式三

将打包命令配置到package.json中:

"scripts": {
	"build": "webpack --config webpack.config.js"
}

运行:

npm run build

五、样例

样例来源:https://github.com/ruanyf/webpack-demos
  • 测试工程 package.json:
{
  "name": "webpack-practice",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
	"build": "webpack --config webpack.config.js"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "babel-core": "^6.21.0",
    "babel-loader": "^6.2.10",
    "babel-preset-es2015": "^6.18.0",
    "babel-preset-react": "^6.16.0",
    "bundle-loader": "^0.5.4",
    "css-loader": "^0.26.1",
    "file-loader": "^0.9.0",
    "html-webpack-plugin": "^2.26.0",
    "jquery": "^3.1.1",
    "open-browser-webpack-plugin": "0.0.3",
    "react": "^15.4.2",
    "react-dom": "^15.4.2",
    "style-loader": "^0.13.1",
    "url-loader": "^0.5.7",
    "webpack": "^1.14.0"
  },
  "devDependencies": {
    "express": "^4.14.0",
    "webpack-dev-server": "^1.16.2"
  }
}

1、Hello World

  • 入口文件(main.js)
document.write('<h1>Hello World</h1>');
  • 配置文件(webpack.config.js)
module.exports = {
	entry: './main.js',
	output: {
		filename: 'bundle.js'
	}
};

  • HTML(index.html)
<html>
  <body>
    <script type="text/javascript" src="bundle.js"></script>
  </body>
</html>

2、多入口文件

  • 入口文件(main1.js、main2.js)

main1.js:

document.write('<h1>Hello World</h1>');

main2.js:

document.write('<h2>Hello Webpack</h2>');
  • 配置文件
module.exports = {
	entry: {
		bundle1: './main1.js',
		bundle2: './main2.js'
	},
	output: {
		filename: '[name].js'
	}
};

  • HTML
<html>
  <body>
    <script src="bundle1.js"></script>
    <script src="bundle2.js"></script>
  </body>
</html>

3、使用babel-loader

使用babel-loader加载器来处理.jsx文件。

  • 需要安装的模块

    • babel-core
    • babel-loader
    • babel-preset-es2015
    • babel-preset-react
    • react
    • react-dom
  • 入口文件(main.jsx)

const React = require('react');
const ReactDOM = require('react-dom');

ReactDOM.render(
  <h1>Hello, world!</h1>,
  document.querySelector('#wrapper')
);
  • 配置文件
module.exports = {
	entry: './demo03/main.jsx',
	output: {
		filename: './demo03/bundle.js'
	},
	module:{
		loaders: [
			{
				test: /\.jsx?$/,
				loader: 'babel-loader?presets[]=es2015&presets[]=react'
			}	
		]
	}
};

module.exports = {
	entry: './demo03/main.jsx',
	output: {
		filename: './demo03/bundle.js'
	},
	module: {
		loaders: [
			{
				test: /\.jsx?$/,
				loader: 'babel',
				query: {
					presets: ['es2015', 'react']
				}
			}	
		]
	}
};

webpack-03.config.js文件与node_modules在同一目录中:

  • HTML
<html>
  <body>
    <div id="wrapper"></div>
    <script src="bundle.js"></script>
  </body>
</html>

4、使用css-loader

使用css-loader加载器来处理.css文件。

  • 需要安装的模块

    • style-loader
    • css-loader
  • 入口文件(main.js)

require('./app.css');
  • 样式文件(app.css)
body {
	background-color: lightblue;
}
  • 配置文件
module.exports = {
	entry: './demo04/main.js',
	output: {
		filename: './demo04/bundle.js'
	},
	module: {
		loaders: [
			{
				test: /\.css$/,
				loader: 'style!css'
			}	
		]
	}
};

webpack-04.config.js文件与node_modules在同一目录中:

  • HTML
<html>
  <head>
    <script type="text/javascript" src="bundle.js"></script>
  </head>
  <body>
    <h1>Hello World</h1>
  </body>
</html>

5、使用url-loader

使用url-loader加载器处理图像文件。

  • 需要安装的模块

    • url-loader
    • file-loader
  • 入口文件(main.js)

var img1 = document.createElement("img");
img1.src = require("./images/small.png");
document.body.appendChild(img1);

var img2 = document.createElement("img");
img2.src = require("./images/big.png");
document.body.appendChild(img2);
  • 配置文件

将小于8KB的文件打包到bundle.js中,大于等于8kb的文件输出到指定的目录:

module.exports = {
	entry: './demo05/main.js',
	output: {
		filename: './demo05/bundle.js',
		publicPath: '../'
	},
	module: {
		loaders: [
			{
				test: /\.(jpg|png)$/,
				loader: 'url-loader?limit=8192&name=./demo05/build/images/[hash].[ext]'
			}
		]
	}
};

其中:loader属性的查询参数name的值为图片打包后的输出路径;图片的加载路径为output配置项的publicPath属性值拼接loader的name属性值后的路径:

function(module, exports, __webpack_require__) {
	module.exports = __webpack_require__.p + "./demo05/build/images/4853ca667a2b8b8844eb2693ac1b2578.png";
}

webpack-05.config.js文件与node_modules在同一目录中:

  • HTML
<html>
  <body>
    <script type="text/javascript" src="bundle.js"></script>
  </body>
</html>

6、使用CSS模块

  • 样式文件(app.css)
.h1 {
	color:red;
}

:global(.h2) {
	color: blue;
}
  • 入口文件(main.jsx)
var React = require('react');
var ReactDOM = require('react-dom');
var style = require('./app.css');

ReactDOM.render(
  <div>
    <h1 className={style.h1}>Hello Local</h1>
    <h2 className="h2">Hello Global</h2>
  </div>,
  document.getElementById('example')
);
  • 配置文件
module.exports = {
	entry: './demo06/main.jsx',
	output: {
		filename: './demo06/bundle.js'
	},
	module: {
		loaders: [
			{
				test: /\.css$/,
				loader: 'style!css?module'
			},
			{
				test: /\.jsx?$/,
				loader: 'babel',
				query: {
					presets: ['es2015', 'react']
				}
			}
		]
	}
};

webpack-06.config.js文件与node_modules在同一目录中:

  • HTML
<html>
	<body>
		<h1 class="h1">Hello World (local)</h1>
		<h2 class="h2">Hello Webpack (global)</h2>
		<div id="example"></div>
		<script src="./bundle.js"></script>
	</body>
</html>

7、使用Webpack-UglifyJsPlugin插件

  • 入口文件
var longVariableName = 'Hello';
longVariableName += ' World';
document.write('<h1>' + longVariableName + '</h1>');
  • 配置文件
var webpack = require('webpack');
var UglifyJsPlugin = webpack.optimize.UglifyJsPlugin;

module.exports = {
	entry: './demo07/main.js',
	output: {
		filename: './demo07/bundle.js'
	},
	plugins: [
		new UglifyJsPlugin({
			compress: {
				warnings: false
			}
		})	
	]
};

webpack-07.config.js文件与node_modules在同一目录中:

  • HTML
<html>
<body>
  <script src="bundle.js"></script>
</body>
</html>

8、使用Html-Webpack-Plugin和Open-Browser-Webpack-Plugin

  • 需要安装的模块

    • html-webpack-plugin

      创建html文件

    • open-browser-webpack-plugin

      当使用webpack-dev-server启动服务时打开浏览器

    • webpack-dev-server

        npm install webpack-dev-server --save-dev
      
  • 入口文件

document.write('<h1>Hello World</h1>');
  • 配置文件
var HtmlWebpackPlugin = require('html-webpack-plugin');
var OpenBrowserPlugin = require('open-browser-webpack-plugin');

module.exports = {
	entry: './demo08/main.js',
	output: {
		filename: './demo08/bundle.js'
	},
	plugins: [
		new HtmlWebpackPlugin({
			title: 'Webpack-demos',
			filename: './demo08/index.html'
		}),
		new OpenBrowserPlugin({
			url: 'http://localhost:8080/demo08/index.html'
		})
	]
};

webpack-08.config.js文件与node_modules在同一目录中:

  • HTML

HTML文件内容由Webpack打包时自动创建:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Webpack-demos</title>
  </head>
  <body>
  <script type="text/javascript" src=".././demo08/bundle.js"></script></body>
</html>
  • 启动服务

运行以下命令会自动启动服务并打开浏览器:

webpack-dev-server --config webpack-08.config.js

9、环境标识(Environment flags)

  • 入口文件
document.write('<h1>Hello World</h1>');

if (__DEV__) {
  document.write(new Date());
}
  • 配置文件
var webpack = require('webpack');

var devFlagPlugin = new webpack.DefinePlugin({
	__DEV__: JSON.stringify(JSON.parse(process.env.DEBUG || 'true'))
});

module.exports = {
	entry: './demo09/main.js',
	output: {
		filename: './demo09/bundle.js'
	},
	plugins: [
		devFlagPlugin	
	]
};

webpack-09.config.js文件与node_modules在同一目录中:

  • 设置环境变量

    • Linux & Mac
      env DEBUG=true
    
    • Windows
      set DEBUG=true
    
  • HTML

<html>
<body>
  <script src="bundle.js"></script>
</body>
</html>
  • 运行效果

未设置环境变量DEBUG,默认为true:

设置环境变量DEBUG为false:

set DEBUG=false
webpack-dev-server --config webpack-09.config.js

10、代码文件分割(Code Splitting)

代码文件分割是指在打包时将部分模块分开打包到另一个bundle中,这个新的bundle会被webpack通过jsonp来按需加载。

  • require.ensure

webpack在编译时会静态解析代码中的require.ensure(),将其指定的模块打包到另一个bundle中。

require.ensure(dependencies: String[], callback: function(require), chunkName: String)
  • JS模块

a.js:

module.exports = 'Hello World';

b.js:

console.log('此模块总是会被加载');
  • 入口文件
require('./b');

document.querySelector('#mybtn').addEventListener('click', function(){
	require.ensure(['./a'], function(require) {
		var content = require('./a');
		document.write('<h1>' + content + '</h1>');
	});
}, false);
  • 配置文件
module.exports = {
	entry: './demo10/main.js',
	output: {
		filename: './demo10/bundle.js',
		publicPath: '../'
	}
};

webpack-10.config.js文件与node_modules在同一目录中:

  • HTML
<html>
	<body>
		<button id="mybtn">load</button>
		<script src="bundle.js"></script>
	</body>
</html>

页面加载后,在Console中可以看到:”此模块总是会被加载”;点击load按钮时,才会下载/1../demo10/bundle.js(模块a.js打包后的内容),此JS下载执行后,HTML文档中的内容会被替换为:”Hello World”。

11、使用bundle-loader实现Coding Splitting

备注:与样例10类似
  • 需要安装的模块

bundle-loader

  • JS模块

a.js:

module.exports = 'Hello World';

b.js:

console.log('此模块总是会被加载');
  • 入口文件
require('./b');

document.querySelector('#mybtn').addEventListener('click', function(){
	var load = require('bundle-loader!./a.js');
	load(function(file) {
		document.open();
		document.write('<h1>' + file + '</h1>');
		document.close();
	});
}, false);
  • 配置文件
module.exports = {
	entry: './demo11/main.js',
	output: {
		filename: './demo11/bundle.js',
		publicPath: '../'
	}
};

webpack-11.config.js文件与node_modules在同一目录中:

  • HTML
<html>
	<body>
		<button id="mybtn">load</button>
		<script src="bundle.js"></script>
	</body>
</html>

效果与样例10相同:

12、通用代码提取(Commons Chunk)

如果多个脚本文件存在通用的chunk(代码块)时,可以使用CommonsChunkPlugin插件将通用的代码提取到单独的文件中。

  • JS模块(c.js)
console.log('This is common data...');
  • 入口文件(main1.jsx、main2.jsx)

main1.jsx:

var React = require('react');
var ReactDOM = require('react-dom');

require('./c');

ReactDOM.render(
	<h1>Hello World</h1>,
	document.getElementById('a')
);

main2.jsx:

var React = require('react');
var ReactDOM = require('react-dom');

require('./c');

ReactDOM.render(
	<h2>Hello Webpack</h2>,
	document.getElementById('b')
);
  • 配置文件
var CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin');

module.exports = {
	entry: {
		bundle1: './demo12/main1.jsx',
		bundle2: './demo12/main2.jsx'
	},
	output: {
		filename: './demo12/[name].js'
	},
	module: {
		loaders: [
			{
				test: /\.jsx?$/,
				exclude: /node_modules/,
				loader: 'babel',
				query: {
					presets: ['es2015', 'react']
				}
			}	
		]
	},
	plugins: [
		new CommonsChunkPlugin('./demo12/common.js')		
	]
};

webpack-12.config.js文件与node_modules在同一目录中:

打包后的文件中,通用的代码在common.js文件中,bundle1.js和bundle2.js中只打包了两个入口文件各自特有的内容。

  • HTML
<html>
	<body>
		<div id="a"></div>
		<div id="b"></div>

		<script src="common.js"></script>
		<script src="bundle1.js"></script>
		<script src="bundle2.js"></script>
	</body>
</html>

13、第三方库提取(Vendor Chunk)

还可以使用CommonsChunkPlugin插件将脚本中的第三方库提取到单独的文件中。

  • 需要安装的模块

    • jquery
      npm install jquery --save
    
  • 入口文件

var $ = require('jquery');
$('h1').text('Hello World');
  • 配置文件
var webpack = require('webpack');

module.exports = {
	entry: {
		app: './demo13/main.js',
		vendor: ['jquery']
	},
	output: {
		filename: './demo13/bundle.js'	
	},
	plugins: [
		new webpack.optimize.CommonsChunkPlugin(/*chunkName*/'vendor', /*filename*/'./demo13/vendor.js')		
	]
};

webpack-13.config.js文件与node_modules在同一目录中:

  • HTML
<html>
  <body>
    <h1></h1>
    <script src="vendor.js"></script>
    <script src="bundle.js"></script>
  </body>
</html>

14、使用ProvidePlugin插件

如果想让一个模块和变量一样在任何模块中可用而不需要使用require('xxx')的方式,可以使用ProvidePlugin插件。

  • JS模块

a.js:

$('h1').text('Hello World');

b.js:

$('h2').text('Hello Webpack');
  • 入口文件
require('./a');
require('./b');
console.log(window.jq('body'));
  • 配置文件
var webpack = require('webpack');

module.exports = {
	entry: {
		app: './demo14/main.js'
	},
	output: {
		filename: './demo14/bundle.js'	
	},
	plugins: [
		new webpack.ProvidePlugin({
			$: 'jquery',
			jQuery: 'jquery',
			'window.jq': 'jquery'
		})
	]
};

webpack-14.config.js文件与node_modules在同一目录中:

  • HTML
<html>
  <body>
    <h1></h1>
	<h2></h2>
    <script src="bundle.js"></script>
  </body>
</html>

15、暴露全局变量(Exposing global variables)

如果需要使用某些全局变量,而且不希望被webpack打包,则可以配置externals属性。

  • JS模块

test.js:含有全局变量的模块,在HTML文件中引入。

var data = 'Hello World';
var otherData = "Webpack";
  • 入口文件(main.jsx)
//tip在webpack.config.js中配置为test.js文件中data变量的值
var tip = require('testmodule');

console.log(tip);

var React = require('react');
var ReactDOM = require('react-dom');

ReactDOM.render(
  <h1>{tip}</h1>,
  document.querySelector('#example')
);
  • 配置文件
module.exports = {
	entry: './demo15/main.jsx',
	output: {
		filename: './demo15/bundle.js'
	},
	module: {
		loaders: [
			{
				test: /\.jsx?$/,
				exclude: /node_modules/,
				loader: 'babel',
				query: {
					presets: ['es2015', 'react']
				}
			}	
		]
	},
	externals: {
		//模块名 : 全局变量名
		testmodule: 'data'
	}
};

webpack-15.config.js文件与node_modules在同一目录中:

  • HTML
<html>
  <body>
	<div id="example"></div>
	<script src="test.js"></script>
    <script src="bundle.js"></script>
  </body>
</html>

附:

webpack

webpack中文

webpack2.2中文文档

webpack-demos

[Configuration webpack](https://webpack.js.org/configuration/)