webpack是一个模块打包器(module bundler),提供了一个核心,核心提供了很多开箱即用的功能,同时它可以用loader和plugin来扩展。webpack本身结构精巧,基于tapable的插件架构,扩展性强,众多的loader或者plugin让webpack显得很复杂。
webpack常用配置包括:devtool、entry、 output、module、resolve、plugins、externals等,本文主要介绍下webpack常用的loader和plugin
webpack允许我们使用loader来处理文件,loader是一个导出为function的node模块。可以将匹配到的文件进行一次转换,同时loader可以链式传递。
loader的使用方式
一般loader的使用方式分为三种:
- 在配置文件webpack.config.js中配置
module.exports = {
module: {
rules: [
{
test: /\.txt$/,
use: 'raw-loader'
}
]
}
}
- 通过命令行参数方式
webpack --module-bind 'txt=raw-loader'
- 通过内联使用
import txt from 'raw-loader!./file.txt';
webpack常用的loader
- 样式:style-loader、css-loader、less-loader、sass-loader等
- 文件:raw-loader、file-loader 、url-loader等
- 编译:babel-loader、coffee-loader 、ts-loader等
- 校验测试:mocha-loader、jshint-loader 、eslint-loader等
比如下面配置,可以匹配.scss的文件,分别经过sass-loader、css-loader、style-loader的处理。
sass-loader
转化sass为css文件,并且包一层module.exports成为一个js module。style-loader
将创建一个style标签将css文件嵌入到html中。css-loader
则处理其中的@import和url()。
module.exports = {
module: {
rules: [
{
test: /\.scss$/,
use:[
{loader:'style-loader'},
{loader:'css-loader',options:{sourceMap:true,modules:true}},
{loader:'sass-loader',options:{sourceMap:true}}
],
exclude:/node_modules/
}
]
}
}
vue-loader、coffee-loader、babel-loader
等可以将特定文件格式转成js模块、将其他语言转化为js语言和编译下一代js语言file-loader、url-loader
等可以处理资源,file-loader可以复制和放置资源位置,并可以指定文件名模板,用hash命名更好利用缓存。url-loader
可以将小于配置limit大小的文件转换成内联Data Url的方式,减少请求。raw-loader
可以将文件以字符串的形式返回-
imports-loader、exports-loader
等可以向模块注入变量或者提供导出模块功能,常见场景是:jquery插件注入,imports-loader?=jquery
禁用AMD,imports-loader?define=false
等同于:var $ = require("jquery") 和 var define = false;
expose-loader
:暴露对象为全局变量
如何写一个loader:官网介绍how to write a loader
下面是一个简单的raw-loader,它可以将文本类文件转成字符串到js文件中。其中this.cacheable、this.value等是loader的api,分别是将结果标记为可缓存和把值传递给下一个loader。
module.exports = function(content) {
this.cacheable && this.cacheable();
this.value = content;
return "module.exports = " + JSON.stringify(content);
}
webpack的plugin比loader强大,通过钩子可以涉及整个构建流程,可以做一些在构建范围内的事情。
webpack常用的plugin
官网介绍plugins
第三方插件awesome-webpack
首先webpack内置
UglifyJsPlugin
,压缩和混淆代码。webpack内置
CommonsChunkPlugin
,提高打包效率,将第三方库和业务代码分开打包。ProvidePlugin
:自动加载模块,代替require和import
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery'
})
html-webpack-plugin
可以根据模板自动生成html代码,并自动引用css和js文件extract-text-webpack-plugin
将js文件中引用的样式单独抽离成css文件mini-css-extract-plugin
是用来替代 extract-text-webpack-plugin 的新一代产品,不光可以自定义抽取规则,还可以配合 webpack 的分块 splitChunks 配置自动抽取DefinePlugin
编译时配置全局变量,这对开发模式和发布模式的构建允许不同的行为非常有用。
new webpack.DefinePlugin({
PRODUCTION: JSON.stringify(true),
VERSION: JSON.stringify("5fa3b9"),
BROWSER_SUPPORTS_HTML5: true,
TWO: "1+1",
"typeof window": JSON.stringify("object")
})
-
HotModuleReplacementPlugin
热更新- 添加HotModuleReplacementPlugin
- entry中添加 "webpack-dev-server/client?http://localhost:8080/",
- entry中添加 "webpack/hot/dev-server"
- (热更新还可以直接用webpack_dev_server --hot --inline,原理也是在entry中添加了上述代码)
CopyWebpackPlugin
复制文件插件webpack 内置的
DllPlugin
和DllReferencePlugin
相互配合,前置第三方包的构建,只构建业务代码,同时能解决Externals多次引用问题。DllReferencePlugin引用DllPlugin配置生成的manifest.json文件,manifest.json包含了依赖模块和module id的映射关系-
babili-webpack-plugin、transform-runtime 、transform-object-rest-spread
- babili-webpack-plugin:构建在babel之上 why
- transform-runtime :解决了babel在每个文件都插入了辅助代码,代码体积过大的问题。
- transform-object-rest-spread:
Transform rest properties for object destructuring assignment and spread properties for object literals
为对象字面量添加解构赋值和spread属性
optimize-css-assets-webpack-plugin
不同组件中重复的css可以快速去重webpack-bundle-analyzer
一个webpack的bundle文件分析工具,将bundle文件以可交互缩放的treemap的形式展示。compression-webpack-plugin
生产环境可采用gzip压缩JS和CSShappypack
:通过多进程模型,来加速代码构建
const os = require('os');
let HappyPack = require('happypack');
let happyThreadPool = HappyPack.ThreadPool({size: os.cpus().length});
exports.plugins = [
new HappyPack({
id: 'jsx',
threadPool: happyThreadPool,
loaders: [ 'babel-loader' ]
}),
new HappyPack({
id: 'coffeescripts',
threadPool: happyThreadPool,
loaders: [ 'coffee-loader' ]
})
];
exports.module.loaders = [
{
test: /\.js$/,
loaders: [ 'happypack/loader?id=jsx' ]
},
{
test: /\.coffee$/,
loaders: [ 'happypack/loader?id=coffeescripts' ]
},
]
写一个webpack插件:
主要的步骤如下:
- 编写一个JavaScript命名函数。
- 在它的原型上定义一个apply方法。
- 指定挂载的webpack事件钩子。
- 处理webpack内部实例的特定数据。
- 功能完成后调用webpack提供的回调。
编写插件之前要理解compiler和compilation两个对象,以及webpack生命周期的各个阶段和钩子,plugin比loader强大,通过plugin你可以访问compliler和compilation过程,通过钩子拦截webpack的执行。
比如我们可以在构建生成文件时,将所有生成的文件名生成到filelist.md的文件中
webpack会将compilation.assets的内容生成文件,所以可以在构建中利用它生成我们想要的文件。
function FileListPlugin(options) {}
FileListPlugin.prototype.apply = function(compiler) {
compiler.plugin('emit', function(compilation, callback) {
var filelist = 'In this build:\n\n';
for (var filename in compilation.assets) {
filelist += ('- '+ filename +'\n');
}
compilation.assets['filelist.md'] = {
source: function() {
return filelist;
},
size: function() {
return filelist.length;
}
};
callback();
});
};
module.exports = FileListPlugin;
比如我们可以在html-webpack-plugin生成文件后刷新页面,完成热更新效果。
var webpack = require('webpack')
var webpackConfig = require('./webpack.config')
var compiler = webpack(webpackConfig)
var hotMiddleware = require('webpack-hot-middleware')(compiler, {
log: () => {}
})
compiler.plugin('compilation', function (compilation) {
compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
hotMiddleware.publish({ action: 'reload' })
cb()
})
})
比如我们可以在构建完成后,打开一个提示窗口。
class Notifier {
apply(compiler) {
compiler.plugin("done", (stats) => {
const pkg = require("./package.json");
const notifier = require("node-notifier");
const time = ((stats.endTime - stats.startTime) / 1000).toFixed(2);
notifier.notify({
title: pkg.name,
message: `WebPack is done!\n${stats.compilation.errors.length} errors in ${time}s`,
contentImage: "https://path/to/your/logo.png",
});
});
}
}
module.exports = Notifier;