常用 loader
常用 plugin
webpack.DefinePlugin
作用:在打包阶段定义全局变量
使用的好处:定义的全局变量可以在业务代码中获取
webpack.HashedModuleIdsPlugin
作用:保持打包文件的资源稳定。
在浏览器下载资源文件的时候获取的源会有几种:
1.从服务器拉取 2. from memeory cache 3. from disk cache
那么浏览器如何判断哪些资源从哪里获取呢?这就是打包文件结尾 hash 值的作用。一旦资源文件发生改变,hash 的值就会变化,浏览器就会重新从服务器拉取。
但是这也会有一个问题,就是每次打包的时候,一些没有改变的文件的 hash 也会发生变化,比较典型的是 vender.js 打包的一般的第三方库如:vue,element-ui,loadash等,这些的内容是不会变得,我们希望这样的文件的 hash 值最好能保持不变,这样就不用每次都从服务重新拉取了。
所以我们会看到,通过 vue-cli 脚手架搭建的项目的 build.js 文件使用的是 chuckhash,chuckhash 可以保持hash值的稳定。但是当增加一些第三方库或者减少一些第三方库,chuckhash 生成的 hash 值还是会变化的。这时候就可以使用这个 HashedModuleIdsPlugin 插件了。
使用方式:在需要打包的环境中增加配置:
plugins: [
new webpack.HashedModuleIdsPlugin()
]
webpack.NoEmitOnErrorsPlugin
这个插件多是在开发环境中配置使用,当代码中有一些错误,会导致开发环境运行失败,这时候增加这个插件,会先将工程运行成功,然后将错误信息打印到浏览器的控制台上,方便查看和调试。
plugins: [
new webpack.NoEmitOnErrorsPlugin()
]
webpack.ProvidePlugin
提供全局的引用库。
当很多组件都在使用某一个第三方js,在组件中都需要 import 进来,这有点繁琐,比如 jquery, axios。这个组件就可以定义这些公共的js,在组件中使用就不需要 import 了。这些文件在打包的时候会直接被打包到 vender 中,只是挂在到 vue 下。
plugins: [
new webpack.ProvidePlugin({
$ : 'jquery',
axios: 'axios'
})
]
copy-webpack-plugin
用来复制静态文件。
webpack 在打包的时候,只会将使用过的文件打包进去,那么如果在 static 文件夹下有 100 张图片,在代码中值使用到了 10 张,那么另外 90 张是不会被打包的。而这些照片可能是需要用的,那么就可以使用这个插件,帮助我们将这些静态文件复制到指定目录下。
plugins: [
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, '../static'),
to: config.build.assetsSubDirectory,
ignore: ['.*']
}
])
]
打包优化
dll 优化
我们使用的第三方库一般是不会修改的,但是每次打包的时候还是会处理这些第三方库,这个过程是比较耗时的。那么能不能将这些第三方库做单独的打包呢?这样每次打包就,除了第一次需要处理这些第三方库,后边再次进行的编译就不要处理这些第三方库了。
const webpack = require('webpack')
module.exports = {
enryt:{
vender: ['jquery', 'loadsh'] // 这个 vender 是打包出来想叫的名字
},
output: {
path: __dirname + '/dll',
filename: '[name].dll.js',
library: '[name]_library' // 这里的那么取的是上面 entry 的命名
},
plugins: [
new webpack.DllPlugin({
path: __dirname + '/dll/[name]_manifest.json',
name: '[name]_library' // 要和上面 library 中的信息一样
})
]
}
然后需要再在 webpack.config.js 中定义插件,通知它 dll 的打包的信息。
const webpack = require('webpack')
const HappyPack = require('happypack')
const os = require('os') // os 是 node 的内置模块
const happyThreadPool = HappyPack.ThreadPool({size: os.cpus().length})//使用 cpu 的核数来做线程的数量
module.exports = {
mode: ''development,
enryt:{
app: './app.js'
},
output: {
filename: '[name].js'
},
module : {
rules: [
{
test: /\.js$/,
// loader: 'babel-loader'
use: [
{
loader: 'happypack/loader?id=happybabel'
},
{
loader: './myloader.js' //执行自定义的 loader
}
]
}
]
},
plugins: [
new webpack.DllReferencePlugin({
manifest: './dll/vender_manifest.json'
}),
new HappyPack({
id: 'happybabel',
loaders: 'babel-loader?cacheDirectory=true',
threadPool: happyThreadPool
}),
new HappyPack({
id: 'happycss',
loaders: 'css-loader?cacheDirectory=true',
threadPool: happyThreadPool
})
]
}
happyPack
node 默认是单线程的,但是其实 node 是可以开启工作进程的,也就是可以多进程工作。使用 happyPack 处理文件很多的工程效果是很明显的,如果是文件很少的文件的化,时长反而可能会增长。
dll 优化的build.js 文件
'use strict'
require('./check-versions')();
process.env.NODE_ENV = 'production';
process.env.BUILD_MODE = '';
if (!!process.argv[2] && process.argv[2] === 'online') {
process.env.BUILD_MODE = 'online'
}
const ora = require('ora');
const rm = require('rimraf');
const path = require('path');
const chalk = require('chalk');
const webpack = require('webpack');
const config = require('../config');
const webpackDllConfig = require('./webpack.dll.config');
const spinner = ora('building for production...');
spinner.start();
function buildDll () {
return new Promise ((resolve, reject) => {
webpack(webpackDllConfig, (err, stats) => {
spinner.stop();
if (err) throw err;
process.stdout.write(stats.toString({
colors: true,
modules: false,
children: false,
chunks: false,
chunkModules: false
}) + '\n\n');
if(stats.hasErrors()) {
console.log(chalk.red(' Build failed with errors.\n'));
reject();
process.exit(1);
}
console.log(chalk.cyan(' Build complete.\n'));
console.log(chalk.yellow(
' Tip: built files are meant to be served over an HTTP server.\n' +
' Opening index.html over file:// won\'t work.\n'
));
resolve();
})
})
}
function buildProject (config) {
return new Promise((resolve, reject) => {
webpack(config, (err, stats) => {
spinner.stop();
if (err) throw err;
process.stdout.write(stats.toString({
colors: true,
modules: false,
children: false, //if you are using ts-loader, setting this to true will make
chunks: false,
chunkModules: false
}) + '\n\n');
if(stats.hasErrors()) {
console.log(chalk.red(' Build failed with errors.\n'));
reject();
process.exit(1);
}
console.log(chalk.cyan(' Build complete.\n'));
console.log(chalk.yellow(
' Tip: built files are meant to be served over an HTTP server.\n' +
' Opening index.html over file:// won\'t work.\n'
));
resolve();
})
})
}
rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
if (err) throw err;
if (process.env.BUILD_MODE === 'online') {
buildDll ()
.then (() => {
return require('./webpack.prod.conf');
})
.then ((config) => {
return buildProject(config);
})
} else {
buildDll ()
.then (() => {
return require('./webpack.test.config')
})
.then ((config) => {
return buildProject(config);
})
}
})
解决方案
如果是对模块内容进行处理: loader 是第一解决方案 这里的模块比如:js 文件, css 文件的某类文件
loader 的本质其实就是执行了一个方法,这个方法接受的参数,就是 test 匹配出来的文件的内容,那么这个loader 就可以针对文件的内容进行操作了。 举例:myloader.js
module.exports = function (context) {
console.log(context),
context.replace('bind', 'on')
return context // 切记,一定要返回
}
如果要增加一些特殊的功能:可以自定义插件
而当有一些需求处理的内容是散落在各种类型的文件中的时候,就需要使用自定义插件了。 例如有一个需求:在开发阶段引用的是 static 下的文件,但是生产上是使用的是文件服务器的文件,那么就需要将 static 的路径替换为 www.xxx.com,这个时候该怎么处理呢?这就是典型的使用 plugin 的场景,因为这个引用可以是散落在任意类型的文件中的。
定义文件 /myplugin/index.js:
const fs = require('fs')
const path = require('path')
module.exports = a
function a () {}
a.prototype.apply = function (compiler) {
// 插件就是取监听webpack 的生命周期,可以在某个生命周期做一些事情
compiler.hooks.done.tap('changeStatic', function (compilation) {
let context = compiler.options.context
let publicPath = path.resolve(context, 'dist')
compilation.toJson().assets.forEach( ast => {
const filePath = path.resolve(publicPath, ast.name)
fs.readFile(filePath, function(err, file) {
let newContext = file.toSring().replace('/static', 'www.xxx.com')
fs.writeFile(filePath, newContext, function() {})
})
})
})
}
项目上的打包简化,可变性配置等:编写相应的操作函数