1. What
Webpack是由Tobias Koppers开发的一个开源前端模块构建工具。Webpack 是当下最热门的前端资源模块化管理和打包工具。它可以将许多松散的模块按照依赖和规则打包成符合生产环境部署的前端资源。还可以将按需加载的模块进行代码分隔,等到实际需要的时候再异步加载。通过 loader 的转换,任何形式的资源都可以视作模块。
webpack已经是大部分前端项目打包工具的首选,grunt、glup、browserify等逐渐沦为辅助甚至完全被替代。
- 对 CommonJS 、AMD 、ES6的语法做了兼容;
- 对js、css、图片等资源文件都支持打包;
- 串联式模块加载器以及插件机制,让其具有更好的灵活性和扩展性,例如提供对CoffeeScript、ES6的支持;
- 可以将代码切割成不同的chunk,实现按需加载,降低了初始化时间;
- 支持 SourceUrls 和 SourceMaps,易于调试;
- 具有强大的Plugin接口,大多是内部插件,使用起来比较灵活;
- webpack使用异步IO并具有多级缓存。这使得webpack很快且在增量编译上更加快
2. Why
为什么选择Webpack,两点原因:
1) 前端需要模块化:JS模块化不仅仅为了提高代码复用性,更是为了让资源文件更合理地进行缓存;
2) AMD与CMD规范日渐衰弱:原因?ES6带来了很强的模块化语法糖。虽然ES6的更多语法糖让JS可能失去了简单的优势,在一些技术社区还偶尔看到一些反ES6的文章,但感觉ES6仍然是未来发展的趋势;
Ø AMD跟CMD慢慢过时的原因?
主要是模块化前端项目的发布打包问题,requireJS跟seaJS都没有一个很好的解决方案。下面配置文件是,我是一个比较典型项目以requireJS做模块化加载。项目初始阶段还好,当随着项目深入,模块切分得越细,最后发布上线的时候,页面对于JS的请求数量竟然多达20个以上。大量的HTTPRequest造成的后果,不用多想,大家都知道。
require.config({
//baseUrl: "scripts/vendor",
paths: {
underscore:'../vendor/underscore.min',
zepto:'../vendor/zepto.min',
backbone:'../vendor/backbone.min',
domReady:'../vendor/domReady',
template:'../vendor/template',
iscroll:'../vendor/iscroll/iscroll',
common:'../common/common'
},
shim: {
underscore: {
exports:'_'
},
zepto: {
exports:'$'
},
backbone: {
deps: ['underscore', 'zepto'],
exports:'Backbone'
}
},
waitSeconds:0
});
require([
'zepto',
'underscore',
'backbone',
'domReady',
'common',
'../controller/homeCtrl',
'../controller/fadeCtrl',
'../controller/mockCtrl'
],
function($, _, backbone, domReady, common, homeCtrl, fadeCtrl, mockCtrl) {
...
}
不使用webpack的话怎么办??
引入的RequireJS的优化方案:r.js Optimizer,r.js同样可以对各个js进行压缩混淆优化,并最终在out配置中合并成一个JS文件,然后在页面中调用。就是说,不管三七二十一,每个页面对应引用的JS,都会被打包成一个JS,但这样的话,一个站点中多个页面之间公用的JS模块就无法缓存起来了。
({
name:"ptMain",
optimize:"uglify", //uglify
`out:"../build/ptMain-build.js",`
removeCombined:true,
paths: {
underscore:'../vendor/underscore.min',
zepto:'../vendor/zepto.min',
backbone:'../vendor/backbone.min',
domReady:'../vendor/domReady',
iscroll:'../vendor/iscroll/iscroll.min'
},
shim: {
underscore: {
exports:'_'
},
zepto: {
exports:'$'
},
backbone: {
deps: ['underscore', 'zepto'],
exports:'Backbone'
}
}
});
3. How to use
l 安装命令
$npm install webpack -g
l 使用webpack
$npm init #会自动生成一个package.json文件
$npm install webpack--save-dev#将webpack增加到package.json文件中
$npm install webpack@1.2.x--save-dev#特定版本
l 安装开发工具
$npm installwebpack-dev-server--save-dev
l 加载加载器,参考4.3
l 加载插件,参考4.4
l 其他命令
$webpack-help
$webpack #执行一次开发时的编译
$webpack--config #默认为webpack.config.js
$webpack -p #执行一次生成环境的编译(压缩)
$webpack -d #让他生成SourceMaps
$webpack--progress#显示编译进度
$webpack--colors #显示静态资源的颜色
$webpack--watch #文件发送变化时重新打包
4.发展历程
u webpack1支持CMD和AMD,同时拥有丰富的plugin和loader,webpack逐渐得到广泛应用
u webpack2相对于webpack最大的改进就是支持ES Module,可以直接分析ES Module之间的依赖关系,而webpack1必须将ES Module转换成CommonJS模块之后,才能使用webpack进行下一步处理。除此之外webpack2支持tree sharking,与ES Module的设计思路高度契合。
u webpack3相对于webpack2,过渡相对平稳,但是新的特性大都围绕ES Module提出,如Scope Hoisting和Magic Comment;
4.1. 从v1迁移到v2
|
V1 |
V2 |
|
resolve.root, resolve.fallback, resolve.modulesDirectories |
resolve.modules, resolve.extensions resolve.* |
|
module.loaders |
兼容,module.rules |
取消module.preLoaders、module.postLoaders |
||
链式 loaders改进 |
在 v1 版本中,loaders 通常被用 |
兼容,使用rule.use配置项,可以设置为一个 loaders 的列表 |
取消-loader后缀 |
|
兼容, module: { rules: [ { use: [ - "style", + "style-loader", - "css", + "css-loader", - "less", + "less-loader", ] } ] } 同时设置 + resolveLoader: { + moduleExtensions: ["-loader"] + } |
json-loader不再需要手动添加 |
|
兼容,不需要配置也可以 |
解决loader使用require.resolve |
|
module: { rules: [ { // ... - loader: require.resolve("my-loader") + loader: "my-loader" } ] }, resolveLoader: { - root: path.resolve(__dirname, "node_modules") } |
UglifyJsPlugin插件 |
UglifyJsPlugin 的 sourceMap 配置项现在默认为 false 而不是 true,这意味着如果你在压缩代码时启用了 source map。 UglifyJsPlugin 的compress.warnings(警告信息) 配置项现在默认为 false 而不是 true。 devtool: "source-map", plugins: [ new UglifyJsPlugin({ + sourceMap: true, + compress: { + warnings: true + } }) ] UglifyJsPlugin不再压缩loaders。在未来很长一段时间里,需要通过设置inimize:true 来压缩 loaders。 plugins: [ + new webpack.LoaderOptionsPlugin({ + minimize: true + }) ] |
|
|
|
4.2. v3新特性
2017-6-20 webpack推出了3.0版本,官方也发布了公告。
ü 升级到webpack3
webpack3几乎与webpack2完美兼容,除了会影响一些插件的使用,官方给出的数据是:98%的用户升级后,没有影响webpack功能的正常使用。升级的时候可能会有一些相关的warning,但是一般不影响使用。
ü Scope Hoisting-作用域提升.
webpack2处理后的每个模块均被一个函数包裹,这样会带来一个问题:降低浏览器中JS执行效率,这主要是闭包函数降低了JS引擎解析速度。于是webpack团队参考Closure Compiler和Rollup JS,将一些有联系的模块,放到一个闭包函数里面去,通过减少闭包函数数量从而加快JS的执行速度。
module.exports= {
plugins: [
//webpack3通过设置这个新特性
newwebpack.optimize.ModuleConcatenationPlugin()
]
};
ü Magic Comments
在webpack2中引入了Code Splitting-Async的新方法import(),用于动态引入ES Module,webpack将传入import方法的模块打包到一个单独的代码块(chunk),但是却不能像require.ensure一样,为生成的chunk指定chunkName,因此在webpack3中提出了Magic Comment用于解决该问题,用法如下:
import(/* webpackChunkName:"my-chunk-name" */'module');
5. 概念
5.1. EntryPoints
打包文件的入口点
语法:entry: {[entryChunkName: string]: string|Array
<string>}
常见场景:
1. 分离应用程序(app)和公共库(vendor) 入口
constconfig= {
entry: {
app:'./src/app.js',
vendors:'./src/vendors.js'
}
};
module.exports=config;
2.多个页面应用程序
constconfig= {
entry: {
pageOne:'./src/pageOne/index.js',
pageTwo:'./src/pageTwo/index.js',
pageThree:'./src/pageThree/index.js'
}
};
5.2. Output
output 选项控制 webpack 如何向硬盘写入编译文件。注意,即使可以存在多个入口起点,但只指定一个输出配置。
用法:将它的值设置为一个对象,包括以下两点:编译文件的文件名(filename)及一个绝对路径(path)
constconfig= {
output: {
filename:'bundle.js',
path:'/home/proj/public/assets'
}
};
module.exports=config;
选项(Options):
Ø output.filename:注意单个入口和多个入口的不同
Ø output.chunkFilename:此选项决定了按需加载的chunk文件的名称
Ø output.path
Ø output.sourceMapFilename:JavaScript 文件的 SourceMap的文件名
5.3. Loaders
loader 是对应用程序中资源文件进行转换。它们是(运行在 Node.js 中的)函数,可以将资源文件作为参数的来源,然后返回新的资源文件。
安装加载器:
$npm install xxx-loader --save-dev
应用程序中有三种方式使用loader:
Ø 通过webpack.config.js配置
Ø 在 require 语句中显示使用
require('style-loader!css-loader?modules!./styles.css');
尽可能使用 module.rules,因为这样可以在源码中减少引用,并且可以更快调试和定位 loader,避免代码越来越糟
Ø 通过 CLI
webpack --module-bind jade --module-bind 'css=style!css'
loader 遵循标准的模块解析。多数情况下,loader将从模块路径(通常将模块路径认为是 npm install, node_modules)解析。
按照约定,loader 通常被命名为 XXX-loader,其中 XXX是上下文的名称,例如 json-loader。
5.4. Plugins
插件目的在于解决 loader 无法实现的其他事。
原理:webpack 插件是一个具有 apply属性的 JavaScript 对象。 apply 属性会被 webpack compiler 调用,并且 compiler 对象可在整个 compilation 生命周期访问。
用法:由于 plugin 可以携带参数/选项,你必须在wepback 配置中,向 plugins 属性传入new 实例。
配置:
constHtmlWebpackPlugin=require('html-webpack-plugin'); //通过 npm 安装
constwebpack=require('webpack'); //访问内置的插件
constpath=require('path');
constconfig= {
entry:'./path/to/my/entry/file.js',
output: {
filename:'my-first-webpack.bundle.js',
path:path.resolve(__dirname, 'dist')
},
plugins: [
newwebpack.optimize.UglifyJsPlugin(),
newHtmlWebpackPlugin({
template:'./src/index.html'
})
]
};
module.exports=config;
5.5. Configuration
webpack 的配置文件是JavaScript 文件导出的一个对象。此对象由 webpack 根据对象定义的属性进行解析。webpack配置是标准的 Node.js CommonJS 模块,你可以如下:
Ø 通过require(...) 导入其他文件
Ø 通过require(...) 使用 npm 的工具函数
Ø 使用 JavaScript控制流表达式,例如 ?: 操作符
Ø 对常用值使用常量或变量
Ø 编写并执行函数来生成部分配置
5.6. Modules
对比 Node.js 模块,webpack 模块能够以各种方式表达它们的依赖关系,几个例子如下:
Ø ES2015 import 语句
Ø CommonJS require() 语句
Ø AMD define 和 require 语句
Ø css/sass/less 文件中的 @import 语句。
Ø 样式(url(...))或 HTML 文件(<imgsrc=...>)中的图片链接(image url)
webpack 1需要特定的loader来转换ES2015 import,然而通过webpack2可以开箱即用。
webpack社区已经为各种流行语言和语言处理器构建了loader,包括:
Ø CoffeeScript
Ø TypeScript
Ø ESNext (Babel)
Ø Sass
Ø Less
Ø Stylus
解析规则:
使用enhanced-resolve,webpack 能够解析三种文件路径:
Ø 绝对路径
import"/home/me/file";
import"C:\\Users\\me\\file";
Ø 相对路径
import"../src/file1";
import"./file2";
Ø 模块路径
import"module";
import"module/lib/file";
模块将在 resolve.modules 中指定的所有目录内搜索。你可以替换初始模块路径通过使用 resolve.alias 配置选项来创建一个别名。解析器(resolver)将检查路径是否指向文件或目录。如果路径指向一个文件:
Ø 如果路径具有文件扩展名,则被直接将文件打包。
Ø 否则,将使用[resolve.extensions] 选项作为文件扩展名来解析,此选项告诉解析器在解析中能够接受哪些扩展名(例如 .js, .jsx)。
如果路径指向一个文件夹,则采取以下步骤找到具有正确扩展名的正确文件。
Ø 如果文件夹中包含package.json 文件,则按照顺序查找 resolve.mainFields 配置选项中指定的字段。并且 package.json 中的第一个这样的字段确定文件路径。
5.7. Targets
因为服务器和浏览器代码都可以用JavaScript编写,所以webpack 提供了多种构建目标(target),你可以在你的 webpack 配置中设置。
用法:
module.exports= {
target:'node'
};
使用 node webpack 会编译为用于「类 Node.js」环境(使用 Node.js 的 require ,而不是使用任意内置模块(如 fs 或 path)来加载 chunk)
注:尽管 webpack 不支持向 target传入多个字符串,你可以通过打包两份分离的配置来创建同构的库
5.8. HotModule Replacement
模块热替换功能会在应用程序运行过程中替换、添加或删除模块,而无需重新加载页面。这使得你可以在独立模块变更后,无需刷新整个页面,就可以更新这些模块,极大地加速了开发时间。webpack-dev-server支持热模式,在试图重新加载整个页面之前,热模式会尝试使用 HMR 来更新。
6. 常见插件
http://www.css88.com/doc/webpack2/plugins/
UglifyJsPlugin,可以优化(支持压缩、混淆)代码.
module.exports= {
entry:"./entry.js",
output: {
path:__dirname,
filename:"bundle.js",
},
plugins: [
//使用丑化js插件
newwebpack.optimize.UglifyJsPlugin({
compress: {
warnings:false
},
mangle: {
except: ['$super','$', 'exports', 'require']
}
})
]
};
以上变量‘$super’, ‘$’, ‘exports’ or ‘require’,不会被混淆
7. 版本控制
对于静态资源的版本控制,目前微信项目采取办法是版本号作为请求参数,版本号为发布日期,但有两个问题:
Ø 更新版本时,CDN不能及时更新;
Ø 没有发生变更的文件也被赋上新版本;
Webpack的做法是,生成hash来区分文件,Computea hash of all chunks and add it.(生成所有代码块的hash)。
//所有代码块添加hash
module.exports= {
entry:"./entry.js",
output: {
path:"assets/[hash]/",
publicPath:"assets/[hash]/",
filename:"bundle.js"
}
};
//单个代码块添加hash
module.exports= {
entry:"./entry.js",
output: {
path:"build/",
publicPath:"build/",
chunkFilename:"[id].[hash].bundle.js",
filename:"output.[hash].bundle.js",
}
};