手记

从0搭建自己的webpack开发环境(五)

往期回顾:

前四篇文章我们已经掌握了webpack各种常见的配置,这一片文章我们来看看如何实现webpack中的优化。

我们先来编写最基本的webpack配置,然后依次实现其中的各种优化。


const  MiniCssExtractPlugin  =  require("mini-css-extract-plugin");

const  HtmlWebpackPlugin  =  require("html-webpack-plugin");

const  path  =  require("path");

module.exports  = mode => {

return {

mode: mode,

entry: "./src/main.js",

output: {

filename: "bundle.js",

path: path.resolve(__dirname, "dist")

},

module: {

rules: [

{

test: /\.(png|jpg|gif)$/,

use: "file-loader"

},

{

test: /\.js$/,

use: "babel-loader"  // .babelrc已经配置支持react

},

{

test: /\.css$/,

use: [

mode  !==  "development"

?  MiniCssExtractPlugin.loader

:  "style-loader",

"css-loader"

]

}

]

},

plugins: [

new  PurgecssPlugin({

paths: glob.sync(`${path.join(__dirname, "src")}/**/*`, { nodir: true }) // 不匹配目录,只匹配文件

}),

mode  !==  "development"  &&

new  MiniCssExtractPlugin({

filename: "css/[name].css"

}),

new  HtmlWebpackPlugin({

template: "./src/template.html",

filename: "index.html"

})

].filter(Boolean)

};

};

.babelrc配置文件


{

"presets": [

"@babel/preset-env",

"@babel/preset-react"

]

}

1.删除无用的Css样式

先来看看编写的代码


import  './style.css'

import  React  from  'react';

import  ReactDOM  from  'react-dom';

ReactDOM.render(<div>hello</div>,document.getElementById('root'));


body{

background: red

}

.class1{

background: red

}

这里的.class1显然是无用的,我们可以搜索src目录下的文件,删除无用的样式


const  glob  =  require('glob');

const  PurgecssPlugin  =  require('purgecss-webpack-plugin');

  

// 这里需要配合mini-css-extract-plugin插件

mode  !==  "development"  &&  new  PurgecssPlugin({

paths: glob.sync(`${path.join(__dirname, "src")}/**/*`, { nodir: true }) // 不匹配目录,只匹配文件

}),

2.图片压缩插件

打包后的图片进行优化


npm install image-webpack-loader --save-dev

file-loader之前使用压缩图片插件


loader: "image-webpack-loader",

options: {

mozjpeg: {

progressive: true,

quality: 65

},

// optipng.enabled: false will disable optipng

optipng: {

enabled: false,

},

pngquant: {

quality: [0.90, 0.95],

speed: 4

},

gifsicle: {

interlaced: false,

},

// the webp option will enable WEBP

webp: {

quality: 75

}

}

可以发现图片大小是否有了明显的变化

3.CDN加载文件

我们希望通过cdn的方式引入资源


const  AddAssetHtmlCdnPlugin  =  require('add-asset-html-cdn-webpack-plugin')

new  AddAssetHtmlCdnPlugin(true,{

'jquery':'https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js'

})

但是在代码中还希望引入jquery来获得提示


import  $  from  'jquery'

console.log('$',$)

但是打包时依然会将jquery进行打包


externals:{

'jquery':'$'

}

在配置文件中标注jquery是外部的,这样打包时就不会将jquery进行打包了

4.Tree-shaking && Scope-Hoisting

4.1 Tree-shaking

顾名思义就是将没用的内容摇晃掉,来看下代码:

main.js


import { minus } from  "./calc";

console.log(minus(1,1));

calc.js


import {test} from  './test';

export  const  sum  = (a, b) => {

return  a  +  b  +  'sum';

};

export  const  minus  = (a, b) => {

return  a  -  b  +  'minus';

};

test.js


export  const  test  = ()=>{

console.log('hello')

}

console.log(test());

观察上述代码会发现我们主要使用minus方法,test.js代码其实是有副作用的!

默认mode:production时,会自动tree-shaking,但是打包后'hello'依然会被打印出来,这时候我们需要配置规避副作用!

package.json中配置


"sideEffects":false,

如果这样设置,默认就不会导入css文件啦,因为我们引入css也是通过import './style.css'的;

这里重点就来了,tree-shaking主要针对es6模块,我们可以使用require语法导入css,但是这样用起来又有点格格不入,所以我们配置css文件不起副作用。


"sideEffects":[

"**/*.css"

]

在开发环境下默认tree-shaking不会生效,可以配置标识提示。


optimization:{

usedExports:true

}

4.2 Scope Hoisting

作用域提升,可以减少代码体积,节约内存


let  a  =  1;

let  b  =  2;

let  c  =  3;

let  d  =  a+b+c;

export  default  d;

// 引入d

import  d  from  './d';

console.log(d);

最终打包后的结果会变成 console.log(6)

  • 代码量明显减少

  • 减少多个函数后内存占用也将减少

5.DllPlugin && DllReferencePlugin

每次构建时第三方模块都需要重新构建,这个性能消耗比较大,我们可以先把第三方库打包成动态链接库,以后构建时只需要查找构建好的库就好了,这样可以大大节约构建时间。


import  React  from  'react';

import  ReactDOM  from  'react-dom';

  

ReactDOM.render(<h1>hello</h1>,document.getElementById('root'))

5.1 DllPlugin

这里我们可以先将reactreact-dom单独进行打包

单独打包创建webpack.dll.js


const  path  =  require('path');

const  DllPlugin  =  require('webpack/lib/DllPlugin');

module.exports  = {

entry:['react','react-dom'],

mode:'production',

output:{

filename:'react.dll.js',

path:path.resolve(__dirname,'dll'),

library:'react'

},

plugins:[

new  DllPlugin({

name:'react',

path:path.resolve(__dirname,'dll/manifest.json')

})

]

}

执行"webpack --config webpack.dll.js命令,可以看到dll目录下创建了两个文件分别是manifest.jsonreact.dll.js

关系是这个酱紫的,到时候我们会通过manifest.json来找到react.dll.js文件中的模块进行加载。

5.2 DllReferencePlugin

在我们的项目中可以引用刚才打包好的动态链接库


const  DllReferencePlugin  =  require('webpack/lib/DllReferencePlugin');

const  AddAssetHtmlWebpackPlugin  =  require('add-asset-html-webpack-plugin');

// 构建时会引用动态链接库的内容

new  DllReferencePlugin({

manifest:path.resolve(__dirname,'dll/manifest.json')

}),

// 需要手动引入react.dll.js

new  AddAssetHtmlWebpackPlugin(

{ filepath: path.resolve(__dirname,'dll/react.dll.js') }

)

使用DllPlugin可以大幅度提高构建速度

6.动态加载

实现点击后动态加载文件


let  btn  =  document.createElement('button');

btn.innerHTML  =  '点击加载视频';

btn.addEventListener('click',()=>{

import('./video').then(res=>{

console.log(res.default);

});

});

document.body.appendChild(btn);

给动态引入的文件增加名字


output:{

chunkFilename:'[name].min.js'

}

import(/* webpackChunkName: "video" */  './video').then(res=>{

console.log(res.default);

})

这样打包后的结果最终的文件就是 video.min.js

7.打包文件分析工具

安装webpack-bundle-analyzer插件


npm install --save-dev webpack-bundle-analyzer

安装后使用插件


const {BundleAnalyzerPlugin} =  require('webpack-bundle-analyzer');

mode  !==  "development"  &&  new  BundleAnalyzerPlugin()

默认就会展现当前应用的分析图表

8.SplitChunks

我们再来看下SplitChunks这个配置,他可以在编译时抽离第三方模块、公共模块。

先将项目配置成多入口文件


entry:{

a:'./src/a.js',

b:'./src/b.js'

}

我们让a,b两个模块同时引用jquery,这里别忘了去掉之前的externals配置

配置SplitChunks插件

默认配置在此,我们一个个描述下含义


splitChunks: {

chunks: 'async', // 分割异步模块

minSize: 30000, // 分割的文件最小大小

maxSize: 0,

minChunks: 1, // 引用次数

maxAsyncRequests: 5, // 最大异步请求数

maxInitialRequests: 3, // 最大初始化请求数

automaticNameDelimiter: '~', // 抽离的命名分隔符

automaticNameMaxLength: 30, // 名字最大长度

name: true,

cacheGroups: { // 缓存组

vendors: { // 先抽离第三方

test: /[\\/]node_modules[\\/]/,

priority: -10

},

default: {

minChunks: 2,

priority: -20, // 优先级

reuseExistingChunk: true

}

}

}

我们将async改为initial

我们在为每个文件动态导入lodash库,并且改成async


import('lodash')

为每个入口引入c.js,并且改造配置文件


splitChunks: {

chunks: 'all',

name: true,

cacheGroups: {

vendors: {

test: /[\\/]node_modules[\\/]/,

priority: -10

},

default: {

minSize:1, // 不是第三方模块,被引入两次也会被抽离

minChunks: 2,

priority: -20,

}

}

}

这样再反过来看chunks的参数是不是就了然于胸了呢?

9.热更新(不是林更新)

模块热替换(HMR - Hot Module Replacement)是webpack提供的最有用的功能之一。它允许在运行时替换、添加、删除各种模块,而无需进行完全刷新重新加载整个页面。

  • 保留在完全重新加载页面时丢失的应用程序的状态;

  • 只更新改变的内容,以节省开发时间;

  • 调整样式更加快速,几乎等同于就在浏览器调试器中更改样式。

启用热更新,默认样式可以支持热更新,如果不支持热更新则可以采用强制刷新。


devServer:{

hot:true

}

new  webpack.NamedModulesPlugin(),

js支持热更新


import  sum  from  './sum';

console.log(sum(1,2));

if(module.hot){ // 如果支持热更新

module.hot.accept(); // 当入口文件变化后重新执行当前入口文件

}

10.IgnorePlugin

配置忽略 importrequire语法


new  webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)

11.费时分析

可以计算每一步执行的运行速度


const  SpeedMeasureWebpackPlugin  =  require('speed-measure-webpack-plugin');

const  smw  =  new  SpeedMeasureWebpackPlugin();

module.exports  =smw.wrap({

...

});

12.noParse

module.noParse,对类似jq这类的依赖库,内部不会引用其他库,那么我们在打包的时候就没有必要再去解析,这样能够增加打包速率。


noParse:/jquery/

13.resolve


resolve: {

extensions: [".js",".jsx",".json",".css"],

alias:{},

modules:['node_modules']

},

14.include/exclude

在使用loader时,可以指定哪些文件不通过loader,或者指定哪些文件必须通过loader


{

test: /\.js$/,

use: "babel-loader",

// include:path.resolve(__dirname,'src'),

exclude:/node_modules/

},

15.happypack

多线程打包,我们可以将不同的逻辑交给不同的线程来处理。


npm install --save-dev happypack

使用插件


const  HappyPack  =  require('happypack');

rules:[

{

test: /\.js$/,

use: 'happypack/loader?id=jsx'

},

  

{

test: /\.less$/,

use: 'happypack/loader?id=styles'

},

]

new  HappyPack({

id: 'jsx',

threads: 4,

loaders: [ 'babel-loader' ]

}),

  

new  HappyPack({

id: 'styles',

threads: 2,

loaders: [ 'style-loader', 'css-loader', 'less-loader' ]

})

16.总结

至此,我们五篇连载《从0搭建自己的webpack开发环境》先要告一段落了,希望大家可以通过这些文章对webpack能有全新的认识,对工作能更有帮助。

0人推荐
随时随地看视频
慕课网APP