2rever的前端小站

webpack学习笔记

Word count: 2,777 / Reading time: 12 min
2019/05/08 Share

webpack学习笔记

  • webpack学习笔记
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 初始化项目
npm init

# 在项目里安装webpack
npm i webpack webpack-cli -D

# 查看webpack 的版本号
npm info webpack

# 安装特定版本的webpack
npm i webpack@4.16.5 webpack-cli -D

# 运行项目内部命令
npx webpack -v

# 以 webpackconfig.js 这个配置文件为webpack配置文件
npx webpack --config webpackconfig.js
  • 新建 webpack.config.js
  • 文件为webpack打包配置文件
  • 配置文件说明
  • webpack.config.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CleanWebpackPlugin = require('clean-webpack-plugin')
const webpack = require('webpack')

module.exports = {
// 如果是development 则不会压缩,如果是production 为压缩的代码
mode: 'development',

// 它是一个映射关系,它知道打包出来的文件实际上对应源代码的哪一段
// devtool: 'cheap-module-eval-source-map',

// 从哪个文件开始打包,即入口文件
entry: {
main: './src/index.js',
// entry: './src/index.js'
},
devServer:{
// 监听服务器的路径
contentBase: './dist',
// 监听命令执行打开页面
open: true,
// 设置服务器开启端口
port: 8999,
// 开启hot module replacement
hot: true,
// 即使不生效,浏览器也不自动刷新
hotOnly: true

// 设置代理
// proxy: {
// '/api':'http://localhost:3000'
// }
} ,
module: {
rules: [{
test: /\.js$/,
// 排除在外的文件
exclude: /node_modules/,
// babel-loader只是webpack和babel之间的通信,但是不能把es6代码转换成es5代码
loader: 'babel-loader',
options: {
// // 配置babel使按需加载
// presets: [['@babel/preset-env',{
// // // 目标浏览器是否需要babel转义
// // targets: {
// // chrome: "67"
// // },
// // 按需加载的配置,一定要配置corejs:3不然报错
// useBuiltIns: 'usage',
// corejs: 3,
// }]]
}
},{
// 字体文件
test: /\.(eot|ttf|svg)$/,
use: {
loader: 'file-loader',
}
},{
test: /\.(png|jpg|gif)$/,
use: {
loader: 'url-loader',
// 占位符
options: {
name: '[name].[ext]',
// 打包到文件夹
outputPath: 'images/',
// 小于limit大小的字节就转成base64
limit: 2048
},
}
}, {
test: /\.less$/,
use: ['style-loader',
{
loader: 'css-loader',
options: {
// 在@import引用的文件也要走两个loader
importLoaders:2,
// 模块化打包
modules: true
}
},
// 安装的时候同时要安装 less less-loader
'less-loader',
// 需要配置postcss.config.js文件引入autoprefixer等工具
'postcss-loader']
}]
},
// htmlWebpackPlugin会在打包结束后,自动生成一个html文件,并把打包生成的js自动引入到这个html当中
plugins: [new HtmlWebpackPlugin({
template: './src/index.html'
}),
new CleanWebpackPlugin(),
// 使用插件webpack自带HotModuleReplacementPlugin
new webpack.HotModuleReplacementPlugin()
],
// 输出的文件
output: {
publicPath: '/',
// 打包输出的js的名字
filename: '[name].js',
// 打包输出的所在绝对路径
path: path.resolve(__dirname, 'dist')
}
}

热加载

  1. webpack --watch
  2. webpack-dev-serve
  3. node server.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const express = require('express')
const webpack = require('webpack')
const webpackMiddleweare = require('webpack-dev-middleware')
const config = require('./webpack.config.js')
// 编译器,监听改变打包
const complier = webpack(config)

const app = express()
app.use(webpackMiddleweare(complier,{
publicPath: config.output.publicPath
}))

app.listen(3000,() => {
console.log('server is running!!')
})

HMR 动态替换代码不刷新的时候需要

1
2
3
4
5
6
if(module.hot) {
module.hot.accept('./number',() =>{
document.body.removeChild(document.querySelect('#number'))
number()
})
}

babel

  • 安装 babel-loader新增webpack和babel的通信
  • 安装 @babel/core@babel/preset-env把es5转换为es6
  • 安装 @babel/polyfill 并 主文件 import '@babel/polyfill' 把没有的函数补充进去,但是会存在主文件打包出来很大,且会污染全局变量
  • 之前用import '@babel/polyfill'
  • 之后用
1
2
	// import "core-js/stable"
// import "regenerator-runtime/runtime"
  • 但是都可以,但是一定要配 corejs:3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
test: /\.js$/,
// 排除在外的文件
exclude: /node_modules/,
// babel-loader只是webpack和babel之间的通信,但是不能把es6代码转换成es5代码
loader: 'babel-loader',
options: {
// 配置babel使按需加载
presets: [['@babel/preset-env',{
// // 目标浏览器是否需要babel转义
// targets: {
// chrome: "67"
// },
// 按需加载的配置,一定要配置corejs:3不然报错
useBuiltIns: 'usage',
corejs: 3,
}]]
}
}
  • 如果是打包类库就不能这样配置babel
  • npm i @babel/runtime -S
  • babel-loader里面要配置options
1
2
3
4
5
6
"plugins": [["@babel/plugin-transform-runtime",{
"corejs":2,
"helpers":true,
"regenerator": true,
"useESModules": false
}]]
  • 如果配置了 corejs:2,需要安装npm i @babel/runtime-corejs2 -S 且不需要在主要的js里面引用 import '@babel/polyfill'等,原来的会污染全局环境,但是这个runtime会以闭包的形式注入js里

  • 如果要配置更多的babel的话,可以新建 .babelrc

  • 把options里面的对象放到 babelrc就可以了

1
2
3
4
5
6
7
8
9
10
11
12
{
// 配置babel使按需加载
"presets": [["@babel/preset-env",{
// // 目标浏览器是否需要babel转义
// targets: {
// chrome: "67"
// },
// 按需加载的配置,一定要配置corejs:3不然报错
"useBuiltIns": "usage",
"corejs": 3,
}]]
}

code splitting

  • 需要配置用插件进行代码分割

    1
    2
    3
    4
    5
    optimization: {
    splitChunks:{
    chunks: 'all'
    }
    },
  • 安装 npm i babel-plugin-dynamic-import-webpack -D来对异步import进行支持

  • .babelrc里配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    {
    // 配置babel使按需加载
    "presets": [
    ["@babel/preset-env",{
    // // 目标浏览器是否需要babel转义
    "targets": {
    "chrome": "67"
    },
    // 按需加载的配置,一定要配置corejs:3不然报错
    "useBuiltIns": "usage",
    "corejs": 3,
    }],
    "@babel/preset-react"
    ],
    "plugins": ["dynamic-import-webpack"]
    }
  • 代码分割和webpack无关

  • webpack中实现代码分割,两种方式:

  1. 同步代码:只需要在webpack.common.js中做optimization的配置
  2. 一部代码(import):异步代码,无需做任何配置,会自动进行代码分割,放置到新的文件中

splitChunksPlugin 配置

  • 做代码分割的时候给打包出来的起名字

    1
    () => import(/* webpackChunkName:"lodash" */ 'lodash')
  • 为了支持这种魔法注释,需要替换掉原来babel插件
    npm i @babel/plugin-syntax-dynamic-import -D

1
"plugins": ["@babel/plugin-syntax-dynamic-import"]
  • 配置splitChunks有很多配置可选择

lazy loading

  • 懒加载是js提出的一个概念,和webpack的关系不大,webpack只不过可以识别这种() => import()语法,然后对这个模块进行代码分割,返回的是一个promise
  • chunk就是每个代码分割出来的js文件

preloading/prefetching

  • 使用magic注释的方式使用,但是可能会有一些兼容问题,在性能优化当中,缓存不是最重要的,因为缓存只是第二次打开的时候才有效果,而应该是code-coverage代码覆盖
    1
    () => import(/* webpackPrefetch:true */ 'lodash')

css的代码分割

  • 如果不是入口文件的js

  • 则走的是 chunkFilename那个入口

    1
    2
    3
    4
    5
    output: {
    filename: '[name].js',
    chunkFilename: '[name].chunk.js'
    path: path.resolve(__dirname,'../dist')
    }
  • 先安装npm i mini-css-extract-plugin -S,然后在webpack里面配置,详情请看文档

  • 因为不支持模块热更新,所以一般在线上打包上使用

  • 线上打包环境里面使用插件的loader替换掉style-loader

  • 注意和tree shaking的冲突

  • 可以在plugins里面的mini-css-extract-plugin插件里面配置导出的css名称

  • 安装npm i optimize-css-asstes-webpack-plugin -D 去做css的压缩

  • 还可配置多页面引用的css分割,在cacheGroups里面配置

打包缓存

  • 对导出的值使用hash去命名,如果不更改源码,就不会更改hash,则浏览器还是会使用缓存

    1
    2
    3
    4
    output: [
    filename: '[name].[contenthash].js',
    chunkFilename: '[name].[contenthash].js'
    ]
  • 在webpack配置以下代码,可以让vendors和main.js之间的关联代码单独打包到runtime里面,防止重新打包的时候更改main.js和 vendor.js的hash值改变

    1
    2
    3
    4
    5
    optimization : {
    runtimeChunk: {
    name:'runtime'
    }
    }

shimming 即webpack的垫片

  • 在下面如果模块使用了$会自动去引入jquery

    1
    2
    3
    4
    5
    6
    7
    const webpack  = require('webpack)

    plugins:[
    new webpack.ProvidePlugin({
    $: 'jquery'
    })
    ]
  • imports-loader,让this总是指向windows

pwa

  • 安装 npm i workbox-webpack-plugin -D 并在配置里配置这个插件

    1
    2
    3
    4
    5
    6
    7
    8
    {
    plugins: [
    new WorkboxPlugin.GenerateSW({
    clientsClaim: true,
    skipWaiting: true
    })
    ]
    }
  • 并在代码里面写相关代码,详情参考插件使用文档

typescript

  • 安装ts-loader并在webpack里面配置
  • 新建tsconfig.json里配置更多相关信息

devserver的proxy代理

  • 详细转发规则看文档,注意historyApiFallback,类似于解决vue路由history模式nginx重定向的问题

eslint 规范项目代码

  • 安装 npm i eslint -D

  • 配置 npx eslint --lint

  • npx eslint src检测src下面的代码

  • 如果要规范react规范则需要配置eslint的配置,详情查看文档

  • 安装npm i babel-eslint -D

  • 这种方式麻烦,另一种简单的方式是在vscode上安装eslint插件,他会根据你的配置文件自动的去提示你

  • 安装npm i eslint-loader -D,并在配置上使用eslint-loader,overlay:true可弹出层报错,详情可看相关文档

webpack性能优化

  1. 跟上技术的迭代(Node,Npm,Yarn)
  2. 在尽可能少的模块上应用loader
  3. Plugin 尽可能精简并确保可靠
  4. resolve参数合理配置,配置resolve可以使引入的后缀省略,及自动查找相应的后缀,详情需要看文档,一般不要配很多
  5. 使用dllplugin提高打包速度,配置webpack.dll.js,并安装npm i add-asset-html-webpack-plugin -S 配置这个插件加载到html新增变量,目标:第三方模块只打包一次,1.第三方模块只打包一次2.我们引入第三方模块的时候,要去使用dll文件引入。即配置new webpack.DllPlugin()new webpack.DllReferencePlugin(),详情看文档
  6. 控制包文件大小
  7. thread-loader,parallel-webpack,happypack多进程打包
  8. 合理使用sourceMap
  9. 结合stats分析打包结果
  10. 开发环境内存编译
  11. 开发环境无用插件剔除

多页面打包配置

  • 使用多个entry配置和htmlwebpackplugin配合使用

自己编写loaders

  • 用例:用try catch包装所有function做异常捕获
  • 根据全局变量,替换中英文等多语言的替换
  • 安装 npm i loader-utils -D
  • 异步代码里面要用 this.async
  • 可以配置resolveLoaders去配置要去使用的loader的目录文件
  • 详情可看相关文档
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // replaceLoaders.js
    const loaderUtils = require('loader-utils')

    module.exports = function(source) {
    console.log(this.query)
    const options = loaderUtils.getOptions(this)
    const result = source.replace('dell',options.name)
    this.callback(null,result)
    }

自己编写plugin

  • loader是用来处理模块,plugin是某些具体时刻上使用的

  • 编写plugin

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    // CopyrightWebpackPlugin.js
    class CopyrightWebpackPlugin {
    constructor(options) {
    // console.log('插件被使用了')
    // console.log(options)
    }
    apply(compiler) {
    // 同步就用下面的写法
    // compiler.hooks.compile.tap("CopyrightWebpackPlugin",(compilation) => {
    // 不需要手动执行cb了
    // })

    // 使用emit时刻,因为是异步所以用tapAsync
    compiler.hooks.emit.tapAsync('CopyrightWebpackPlugin',(compilation,cb) => {
    console.log(compilation.assets)
    // 新增一个txt文件
    compilation.assets['copyright.txt'] = {
    // 内容是什么
    source: function () {
    return 'copy right dell lee'
    },
    // 大概的长度大小
    size: function() {
    return 21
    }
    }
    cb()
    })
    }
    }

    module.exports = CopyrightWebpackPlugin
  • 然后在webpack配置里面引用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    const path = require('path')
    const CopyRightWebpackPlugin = require('./plugins/copyright-webpack-plugin')

    module.exports = {
    mode: 'development',
    entry: {
    main: './src/index.js'
    },
    plugins: [new CopyRightWebpackPlugin()],
    output: {
    path: path.resolve(__dirname,'dist'),
    filename: '[name].js'
    }

    }
CATALOG
  1. 1. webpack学习笔记
  2. 2. 热加载
  3. 3. HMR 动态替换代码不刷新的时候需要
  4. 4. babel
  5. 5. code splitting
  6. 6. splitChunksPlugin 配置
  7. 7. lazy loading
  8. 8. preloading/prefetching
  9. 9. css的代码分割
  10. 10. 打包缓存
  11. 11. shimming 即webpack的垫片
  12. 12. pwa
  13. 13. typescript
  14. 14. devserver的proxy代理
  15. 15. eslint 规范项目代码
  16. 16. webpack性能优化
  17. 17. 多页面打包配置
  18. 18. 自己编写loaders
  19. 19. 自己编写plugin