本文github仓库地址: https://github.com/Rynxiao/webpack-tutorial ,里面包括了本教程的所有代码。
【如果你觉得这篇文章写得不错,麻烦给本仓库一颗星:-D】
1. 导语
1.1 什么叫做webpack
webpack is a module bundler.
webpack takes modules with dependencies and generates static assets representing those modules.
简单的概括就是:webpack是一个模块打包工具,处理模块之间的依赖同时生成对应模块的静态资源。
1.2 webpack可以做一些什么事情
图中已经很清楚的反应了几个信息:
webpack把项目中所有的静态文件都看作一个模块
模快之间存在着一些列的依赖
多页面的静态资源生成(打包之后生成多个静态文件,涉及到代码拆分)
2. webpack安装
全局安装(供全局调用:如
webpack --config webpack.config.js
)npm install -g webpack
项目安装
```javascript
npm install webpack
// 处理类似如下调用
import webpack from "webpack";
var webpack = require("webpack");
```
建议安装淘宝的npm镜像,这样下载npm包会快上很多,具体做法:
// 方式一npm install xx --registry=https://registry.npm.taobao.org/// 方式二:安装淘宝提供的npm工具npm install -g cnpm cnpm install xx// 方式三// 在用户主目录下,找到.npmrc文件,加上下面这段配置registry=https://registry.npm.taobao.org/
3. webpack的基本配置
创建配置文件(webpack.config.js
,执行webpack命令的时候,默认会执行这个文件)
module.export = { entry : 'app.js', output : { path : 'assets/', filename : '[name].bundle.js' }, module : { loaders : [ // 使用babel-loader解析js或者jsx模块 { test : /\.js|\.jsx$/, loader : 'babel' }, // 使用css-loader解析css模块 { test : /\.css$/, loader : 'style!css' }, // or another way { test : /\.css$/, loader : ['style', 'css'] } ] }};
说明一: webpack.config.js
默认输出一个webpack
的配置文件,与CLI
方式调用相同,只是更加简便
说明二: 执行webpack
命令即可以运行配置,先决条件,全局安装webpack
,项目安装各模块loader
说明三: entry
对应需要打包的入口js
文件,output
对应输出的目录以及文件名,module
中的loaders
对应解析各个模块时需要的加载器
一个简单的例子
basic/app.js
require('./app.css');document.getElementById('container').textContent = 'APP';
basic/app.css
* { margin: 0; padding: 0;}#container { margin: 50px auto; width: 50%; height: 200px; line-height: 200px; border-radius: 5px; box-shadow: 0 0 .5em #000; text-align: center; font-size: 40px; font-weight: bold;}
basic/webpack.config.js
/** * webpack打包配置文件 */module.exports = { // 如果你有多个入口js,需要打包在一个文件中,那么你可以这么写 // entry : ['./app1.js', './app2.js'] entry : './app.js', output : { path : './assets/', filename : '[name].bundle.js' }, module : { loaders : [ { test : /\.js$/, loader : 'babel' }, { test : /\.css$/, loader : 'style!css' } ] }};
basic/index.html
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>basic webpack</title></head><body> <div id="container"></div> <script src="./assets/main.bundle.js"></script></body></html>
在basic
文件夹执行webpack
,打包信息如下
生成main.bundle.js
文件,chunk
名称为main
,也是webpack
默认生成的chunk
名
## 4. webapck常用到的各点拆分
### 4.1 entry相关
4.1.1webpack
的多入口配置
上例的简单配置中,只有一个入口文件,那么如果对应于一个页面需要加载多个打包文件或者多个页面想同时引入对应的打包文件的时候,应该怎么做?
entry : { app1 : './app1.js', app2 : './app2.js'}
在multi-entry
文件夹执行webpack
,打包信息如下
可见生成了两个入口文件,以及各自对应的chunk
名
### 4.2 output相关
4.2.1 output.publicPath
output: { path: "/home/proj/cdn/assets/[hash]", publicPath: "http://cdn.example.com/assets/[hash]/"}
引用一段官网的话:
The publicPath specifies the public URL address of the output files when referenced in a browser. For loaders that embed
<script>
or<link>
tags or reference assets like images, publicPath is used as the href or url() to the file when it’s different then their location on disk (as specified by path).
大致意思就是:publicPath
指定了你在浏览器中用什么地址来引用你的静态文件,它会包括你的图片、脚本以及样式加载的地址,一般用于线上发布以及CDN部署的时候使用。
比如有下面一段配置:
var path = require('path');var HtmlWebpackPlugin = require('html-webpack-plugin');module.exports = { entry : './app.js', output : { path : './assets/', filename : '[name].bundle.js', publicPath : 'http://rynxiao.com/assets/' }, module : { loaders : [ { test : /\.js$/, loader : 'babel' }, { test : /\.css$/, loader : 'style!css' } ] }, plugins : [ new HtmlWebpackPlugin({ filename: './index-release.html', template: path.resolve('index.template'), inject: 'body' }) ]};
其中我将publicPath
设置成了http://rynxiao.com/assets/
,其中设置到了插件的一些东西,这点下面会讲到,总之这个插件的作用是生成了上线发布时候的首页文件,其中script
中引用的路径将会被替换。如下图:
4.2.2 output.chunkFilename
各个文件除了主模块以外,还可能生成许多额外附加的块,比如在模块中采用代码分割就会出现这样的情况。其中chunkFilename
中包含以下的文件生成规则:
[id] 会被对应块的id替换.
[name] 会被对应块的name替换(或者被id替换,如果这个块没有name).
[hash] 会被文件hash替换.
[chunkhash] 会被块文件hash替换.
例如,我在output中如下设置:
output : { path : './assets/', filename : '[name].[hash].bundle.js', chunkFilename: "chunk/[chunkhash].chunk.js"}
同时我修改了一下basic/app.js
中的文件
require('./app.css');require.ensure('./main.js', function(require) { require('./chunk.js');});document.getElementById("container").textContent = "APP";
其中对应的chunk.js
就会生成带有chunkhash
的chunk
文件,如下图:
这在做给文件打版本号的时候特别有用,当时如何进行hash
替换,下面会讲到
4.2.3 output.library
这个配置作为库发布的时候会用到,配置的名字即为库的名字,通常可以搭配libraryTarget
进行使用。例如我给basic/webpack.config.js
加上这样的配置:
output : { // ... library : 'testLibrary' // ...}
那么实际上生成出来的main.bundle.js
中会默认带上以下代码:
var testLibrary = (//....以前的打包生成的代码);// 这样在直接引入这个库的时候,就可以直接使用`testLibrary`这个变量
4.2.4 output.libraryTarget
规定了以哪一种方式输出你的库,比如:amd/cmd/或者直接变量,具体包括如下
"var"
- 以直接变量输出(默认library方式) var Library = xxx (default)
"this"
- 通过设置this
的属性输出 this["Library"] = xxx
"commonjs"
- 通过设置exports
的属性输出 exports["Library"] = xxx
"commonjs2"
- 通过设置module.exports
的属性输出 module.exports = xxx
"amd"
- 以amd方式输出
"umd"
- 结合commonjs2/amd/root
例如我以umd
方式输出,如图:
### 4.3 module相关
4.3.1 loader
中!
代表的含义
require("!style!css!less!bootstrap/less/bootstrap.less");
// => the file "bootstrap.less" in the folder "less" in the "bootstrap"
// module (that is installed from github to "node_modules") is
// transformed by the "less-loader". The result is transformed by the
// "css-loader" and then by the "style-loader".
// If configuration has some transforms bound to the file, they will not be applied.
代表加载器的流式调用,例如:
{ test : /\.css|\.less$/, loader : 'style!css!less' }
就代表了先使用less加载器来解释less文件,然后使用css加载器来解析less解析后的文件,依次类推
4.3.2 loaders
中的include
与exclude
include
表示必须要包含的文件或者目录,而exclude
的表示需要排除的目录
比如我们在配置中一般要排除node_modules
目录,就可以这样写
{ test : /\.js$/, loader : 'babel', exclude : nodeModuleDir }
官方建议:优先采用include,并且include最好是文件目录
4.3.3 module.noParse
使用了noParse
的模块将不会被loaders
解析,所以当我们使用的库如果太大,并且其中不包含require
、define
或者类似的关键字的时候(因为这些模块加载并不会被解析,所以就会报错),我们就可以使用这项配置来提升性能。
例如下面的例子:在basic/
目录中新增no-parse.js
var cheerio = require('cheerio');module.exports = function() { console.log(cheerio);}
webpack.config.js
中新增如下配置:
module : { loaders : [ { test : /\.js$/, loader : 'babel' }, { test : /\.css$/, loader : 'style!css' } ], noParse : /no-parse.js/}
当执行打包后,在浏览器中打开index.html
时,就会报错require is not defined
4.4 resolve相关
4.4.1 resolve.alias
为模块设置别名,能够让开发者指定一些模块的引用路径。对一些经常要被import或者require的库,如react,我们最好可以直接指定它们的位置,这样webpack可以省下不少搜索硬盘的时间。
例如我们修改basic/app.js
中的相关内容:
var moment = require("moment");document.getElementById("container").textContent = moment().locale('zh-cn').format('LLLL');
加载一个操作时间的类库,让它显示当前的时间。使用webpack --profile --colors --display-modules
执行配置文件,得到如下结果:
其中会发现,打包总共生成了104个隐藏文件,其中一半的时间都在处理关于moment
类库相关的事情,比如寻找moment
依赖的一些类库等等。
在basic/webpack.config.js
加入如下配置,然后执行配置文件
resolve : { alias : { moment : 'moment/min/moment-with-locales.min.js' }}
有没有发现打包的时间已经被大大缩短,并且也只产生了两个隐藏文件。
配合module.noParse
使用
module.noParse
参看上面的解释
noParse: [/moment-with-locales/]
执行打包后,效果如下:
是不是发现打包的时间进一步缩短了。
配合externals
使用
externals
参看下面的解释
Webpack 是如此的强大,用其打包的脚本可以运行在多种环境下,Web 环境只是其默认的一种,也是最常用的一种。考虑到 Web 上有很多的公用 CDN 服务,那么 怎么将 Webpack 和公用的 CDN 结合使用呢?方法是使用 externals 声明一个外部依赖。
externals: { moment: true}
当然了 HTML 代码里需要加上一行
<script src="//apps.bdimg.com/libs/moment/2.8.3/moment-with-locales.min.js"></script>
执行打包后,效果如下:
4.4.2 resolve.extensions
resolve : { extensions: ["", ".webpack.js", ".web.js", ".js", ".less"]}
这项配置的作用是自动加上文件的扩展名,比如你有如下代码:
require('style.less');var app = require('./app.js');
那么加上这项配置之后,你可以写成:
require('style');var app = require('./app');
4.5 externals
当我们想在项目中require一些其他的类库或者API,而又不想让这些类库的源码被构建到运行时文件中,这在实际开发中很有必要。此时我们就可以通过配置externals参数来解决这个问题:
//webpack.config.jsmodule.exports = { externals: { 'react': 'React' }, //...}
externals对象的key是给require时用的,比如require('react'),对象的value表示的是如何在global(即window)中访问到该对象,这里是window.React。
同理jquery的话就可以这样写:'jquery': 'jQuery',那么require('jquery')即可。
HTML中注意引入顺序即可:
<script src="react.min.js" /><script src="bundle.js" />
4.6 devtool
提供了一些方式来使得代码调试更加方便,因为打包之后的代码是合并以后的代码,不利于排错和定位。其中有如下几种方式,参见官网devtool
例如,我在basic/app.js
中增加如下配置:
require('./app.css');// 新增hello.js,显然在文件夹中是不会存在hello.js文件的,这里会报错require('./hello.js');document.getElementById("container").textContent = "APP";
执行文件,之后运行index.html
,报错结果如下:
给出的提示实在main.bundle.js第48行,点进去看其中的报错如下:
从这里你完全看不出到底你程序的哪个地方出错了,并且这里的行数还算少,当一个文件出现了上千行的时候,你定位bug
的时间将会更长。
增加devtool
文件配置,如下:
module.exports = { devtool: 'eval-source-map', // ....};
执行文件,之后运行index.html
,报错结果如下:
这里发现直接定位到了app.js
,并且报出了在第二行出错,点击去看其中的报错如下:
发现问题定位一目了然。
5. webpack常用技巧
### 5.1 代码块划分
5.1.1 Commonjs采用require.ensure
来产生chunk
块
require.ensure(dependencies, callback);//static importsimport _ from 'lodash'// dynamic importsrequire.ensure([], function(require) { let contacts = require('./contacts')})
这一点在output.chunkFileName
中已经做过演示,可以去查看
5.1.2 AMD采用require
来产生chunk
块
require(["module-a", "module-b"], function(a, b) { // ...});
5.1.3 将项目APP代码与公共库文件单独打包
我们在basic/app.js
中添加如下代码
var $ = require('juqery'), _ = require('underscore');//.....
然后我们在配置文件中添加vendor
,以及运用代码分离的插件对生成的vendor
块重新命名
var webpack = require("webpack");module.exports = { entry: { app: "./app.js", vendor: ["jquery", "underscore", ...], }, output: { filename: "bundle.js" }, plugins: [ new webpack.optimize.CommonsChunkPlugin(/* chunkName= */"vendor", /* filename= */"vendor.bundle.js") ]};
运行配置文件,效果如下:
5.1.4 抽取多入口文件的公共部分
我们重新建立一个文件夹叫做common
,有如下文件:
// common/app1.jsconsole.log("APP1");
// common/app2.jsconsole.log("APP2");
打包之后生成的app1.bundle.js
、app2.bundle.js
中会存在许多公共代码,我们可以将它提取出来。
// common/webpack.config.js/** * webpack打包配置文件 * 抽取公共部分js */var webpack = require('webpack');module.exports = { http://www.cnblogs.com/rynxiao/p/7149274.html