在webpack性能优化(上)中,咱们从 代码分离,Loader, webpack解析(resolve), webpack 外部扩展(Externals) ,Dlls 优化构建速度,等方面分析了优化手段,这篇文章让咱们接着来撸。javascript
dev prod
一般来讲,咱们会经过使用file-loader,url-loader等loader来处理项目中的静态文件,如图片字体等文件css
//这样最终dist文件中就会生成font文件夹存放字体文件
{
test: /\.(woff|svg|eot|ttf)\??.*$/,
loader: "url-loader",
options: {
limit: 8192,
name: "font/[name].[hash:6].[ext]"
}
}
复制代码
limit属性是在文件大小超出limit的值才会单独打包,不然使用base64 的方式引用一般适用于小图片,这就是咱们一般的文件处理方式。html
使用base64引入图片的好处是减小http请求数,但相应的问题是base64占用的空间比普通的图片文件大一点。java
固然咱们还有另一种方案,具体作法是将项目的中静态文件统一存放在static文件夹下,最后使用 CopyWebpackPlugin将static文件夹拷贝到dist目录下node
new CopyWebpackPlugin([
{
from: path.resolve(SRC_PATH, 'img'),
to: 'img'
}
]),
复制代码
这样作的好处是咱们的静态资源不通过webpack的处理,能够提高构建速度,但问题也是很明显的,那就是维护的成本增大并可能出现一些意外的状况,好比:react
这样处理的问题是可能开发环境引用路径和打包文件访问图片路径不一致问题,这里能够经过output.publicPath属性来配置解决webpack
output: {
//打包文件中经过相对路径引用的资源都会被配置的路径所替换
publicPath: '/assets/'
}
//对于这种结构的项目固然不合适使用这种方法
|- /static
|– /components
| |– /my-component
| | |– index.jsx
| | |– index.css
| | |– icon.svg
| | |– img.png
复制代码
固然从咱们实际项目的测试效果来看,我只能说这种处理方式并不算是很优秀,仅供参考。web
dev
在开发环境中,咱们比较关注调试的方便程度,而原始webpack打包后的bundle文件中可能包含来自多个文件的内容,对于程序的报错信息每每简单的指向这个bundle文件: express
//1 使用devtool选项配置,有多个选项可选
module.exports = {
devtool: 'inline-source-map',
};
//2 使用plugins方式进行更细粒度的配置
module.exports = {
plugins: [
new webpack.SourceMapDevToolPlugin({
filename: '[name].js.map',
exclude: ['vendor.js']
})
]
};
//在使用uglifyjs-webpack-plugin时 须要开启sourceMap选项
复制代码
devtool文档json
prod
对于js压缩 在webpack <= 3.x的版本中:
//1 使用 -p(production)标记来压缩js
//2 使用内置 plugin(webpack.optimize.UglifyJsPlugin)
//3 使用外部引入plugin(uglifyjs-webpack-plugin)
module.exports = {
plugins: [
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
},
output: {
//remove all comments
comments: false
}
}),
]
};
复制代码
在webpack4中
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
module.exports = {
//1 设置 mode
mode: ""production
//2 minimize
optimization:{
minimize: true,
//3 或者指定其余插件
minimizer: [
new UglifyJsPlugin({
sourceMap: true
})
]
},
//3 若须要sourceMap 须要设置 devtool的值
devtool: 'source-map',
};
//可选的压缩插件
UglifyJSPlugin, ClosureCompilerPlugin
BabelMinifyWebpackPlugin,
复制代码
此处须要注意。如果在使用了UglifyJSPlugin且开启sourceMap后,须要同时给devtool设置值。一样的如果设置了devtool的值,则UglifyJSPlugin也须要开启sourceMap。不然不会生成.map的源代码对应文件。
在开启js的压缩后 咱们的tree shaking登场了,tree shaking是什么?为何须要使用?
tree shaking是一个术语,用于描述移除js中未引用的代码。 使用它能优化输出。 未开启tree shaking的实例:
//tool.js
export function square(x) {
return x * x;
}
export function cube(x) {
return x * x * x;
}
//index.js
import { square } from "./tool.js"
//最终输出 在关闭 UglifyJSPlugin插件后测试结果
复制代码
咱们能够看到 cube这个咱们并无引用的模块也被打包进源码了。
使用tree shaking 来优化输出,在package.json中:
//webpack4
//1 将文件标记为无反作用
{
"name": "web",
"version": "1.0.0",
//如果整个项目都无反作用 直接设置为false
"sideEffects": false
//如果部分文件确实有反作用
"sideEffects": [file_path1, file_path2]
}
//2 开启js压缩 使用上述方法开启便可
//webpack2/3
//1 须要配置 .babelrc modules false
{
"presets": [
[
"env",
{
"modules": false
}
]
]
}
//2 开启js压缩
复制代码
「反作用」的定义是,在导入时会执行特殊行为的代码,而不是仅仅暴露一个 export 或多个 export。举例说明,例如 polyfill,它影响全局做用域,而且一般不提供 export。
对于开启后的压缩代码中,咱们搜索"*"号 只获得一个结果,测试成功。
最后咱们简单解释下设置modules false的做用。tree shaking自己是依赖于ES6的静态导入,也就是咱们经常使用的import export。ES6模块化中一个文件可以输出多个模块,而咱们能够只导入须要的模块。对比commonjs的动态导入模块化标准,一个文件只有一个输出,所以不难发现,tree shaking在commonjs模块化的系统中是发挥不了做用的。
而modules的意义是启用将ES6模块语法转换为另外一种模块类型,默认值'commonjs',将该设置为false即不转化,也就是ES6模块语法,因此在此咱们须要将modules设置为false。
modules的取值有 'amd' | 'umd' | 'systemjs' | 'commonjs' | false。 在webpack4中已经能够不用此方法来检测重复模块了
prod
一个web项目中css是关键的一环,若没有额外配置css最终会被打包进入js文件中,但熟悉浏览器渲染的开发者应该会清楚,浏览器在渲染页面时会解析DOM树和CSS树,最后将之对应合并呈现渲染好的页面。将css放在js中引入势必会延缓css树的计算。 因此将css从js中分离,打包成单独的css文件,而后和js并行加载是咱们项目的一个提高点,这样能够加快界面渲染速度,也能够单独作缓存。
//使用插件 extract-text-webpack-plugin
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
//用于css未被提取(allChunks: false)
fallback: "style-loader",
use: 'css-loader'
})
}
new ExtractTextPlugin({
filename: 'common.[chunkhash].css',
allchunk: true
})
复制代码
固然webpack-dev-server是不支持extract-text-webpack-plugin抽离的css热替换的,因此此插件不建议再dev环境中使用,若是非要使用能够考虑css-hot-loader。
prod
webpack将打包的文件放在dist文件夹中,如果使用了hash文件名,则每次文件变更后从新打包生成的文件名都会不一样,这会形成dist目录愈来愈混乱,好的作法是每次打包前先清理dist文件夹:
new CleanWebpackPlugin(pathsToClean, cleanOptions)
复制代码
dev
webpack-dev-server你们都不陌生,开发环境必备,webpack内部依赖了webpack-hot-middleware,webpack-dev-middleware两个插件。
webpack-dev-middleware提供了在内存中编译功能,它在文件更改后自动编译文件并保存在内存中,具体表现为,刷新浏览器便可看到咱们的更改。
webpack-hot-middleware提供了服务端推送功能,一般和webpack-dev-middleware配合使用,当文件更改并自动编译完成后,服务端经过SSE(服务端发送事件)将更改信息推送到客户端,客户端会接收到一个json文件,其中包含了更改了的文件的一些信息,客户端会根据这些信息主动向服务端获取最新的文件。
若无文件更改webpack-hot-middleware也会在必定时间间隔后遍历内存文件检测是否更改,而后经过事件流的方式向客户端发送消息。
webpack-dev-middleware和webpack-hot-middleware都是express的标准插件
我相信各位项目中这两个功能都是已经开启的我就再也不具体说他们的配置了,这里咱们主要说下在node服务端怎么使用这两个插件达到热更新的目的。
咱们以koa为例,如何在koa中开启热更新调试咱们的项目呢?
//新建 app.js做为koa服务端入口 app.js
import Koa from "koa";
import views from "koa-views";
import webpack from "webpack";
import webpack_config from "../webpack/webpack.config.js";
import { devMiddleware, hotMiddleware } from 'koa-webpack-middleware'
var app = new Koa()
var compiler = webpack(webpack_config)
app.use(views("./template", {map: {html: "ejs"}}));
app.use(devMiddleware(compiler,{
publicPath:"/"
}));
app.use(hotMiddleware(compiler))
复制代码
koa-webpack-middleware 将express的中间件(webpack-dev-middleware和webpack-hot-middleware)进行封装,将咱们koa中间件的next方法传递到express的第三个参数中进行封装。
最简单的配置如上。但这种配置会有一个问题就是刷新404的问题。
hotMiddleware会在匹配到项目跟路由时直接返回内存中的html文件给客户端。可是其余的路由如react的路由时,它不会去匹配,最终会返回一个404
//会返回template/index.html 但这时是空的
//也就是没有导入js的html
await ctx.render("index");
复制代码
解决,当用户访问时在webpack编译输出的最后阶段获取到文件信息,取出获取到的html文件写入template下的index.html文件,最后返回它,具体操做以下:
compiler.plugin("emit",(comilation,callback) => {
const assets = comilation.assets;
let file, data;
Object.keys(assets).forEach(key => {
if(key.match(/\.html$/)){
file = path.resolve(__dirname,"./template/index.html");
data = assets[key].source();
fs.writeFileSync(file,data);
}
});
callback();
})
复制代码
固然上述方法略显笨重,且须要理解的东西较多,不太推荐,这里有一个插件能解决上述问题 connect-history-api-fallback,你们本身学习下便可。