webpack4.x => vue.config.js但是 !== vue.config.js
为什么要用webpack,你用的脚手架大部分都是基于这个东西,而这个东西基于nodejs,而nodejs依赖v8引擎,而v8引擎就是v8引擎。
webpack4.x从安装开始
install
1 | npm install webpack -webpack-cli -D |
解释:为什么webpack和webpack-cli需要分开安装,在webpack3中,webpack本身和它的CLI以前都是在同一个包中,但在第4版中已经将两者分开来更好地管理它们。webpack建议装在局部环境,虽然装在全局比较方便,但是当多个项目使用的webpack版本不一样的时候就会出现问题,即使把每个项目的webpack版本都改成一样的,又会导致原本的项目出问题,所以还是建议安装在局部环境,每个项目有独立的webpack。
mode
production:生产模式,线上的环境
development:开发模式,开发环境
sourcemap
官网介绍
mode development ‘cheap-module-eval-souce-map’ 提示比较强,打包速度比较快
mode production ‘cheap-module-souce-map’
entry
入口:每个 HTML 页面都有一个入口起点。单页应用(SPA):一个入口起点,多页应用(MPA):多个入口起点。
单个文件打包:1
entry:"index.js"
多个文件打包成多个:1
2
3
4entry:{
one:'index1.js',
two:'index2.js'
}
多个文件打包成单个:1
2
3entry: {
main:["index1.js","index2.js"]
}
动态入口:1
entry: () => 'index.js'
多个动态入口:1
entry: () => new Promise((resolve) => resolve(['index1.js', 'index2.js']))
output
出口:output 位于对象最顶级键(key),包括了一组选项,指示 webpack 如何去输出、以及在哪里输出你的「bundle、asset 和其他你所打包或使用 webpack 载入的任何内容」。output需要依赖node的path模块来指定当前项目的根目录。
单页面应用的输出:1
2
3
4output:{
path:path.resolve(__dirname,'dist'), //输出目录
filename:'bundle' //输出文件名
}
多页面输出:1
2
3
4output:{
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
}
[name]是根据入口entry设置的变量来输出对应的文件名
配置项大概这么多:
filename,输出的文件名,可以自定义一些名称规则
path,配置输出文件存放在本地的目录
publicPath,配置CDN的路径
chunkFilename ,处理异步加载时的命名规则
hash、chunkhash和contenthash三者的区别
hash是项目级别的,每次构建得出的hash都是相同的,这可能不利于文件的缓存
chunkhash是文件级别的,值是变动修改的文件的chunkhash值
contenthash是文件级别的,在拆分css文件时记得使用处理css的缓存
module
模块:决定了如何处理项目中的不同类型的模块。防止 webpack 解析那些任何与给定正则表达式相匹配的文件。忽略的文件中不应该含有 import, require, define 的调用,或任何其他导入机制。忽略大型的 library 可以提高构建性能。这个参数还是有挺多的,可以去官网看
比如在安装完webpack之后项目目录中肯定多了一个叫node_modules的文件夹,里头有很多模块是不需要打包的,同时要识别jsx文件1
2
3
4
5rules: [{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/
}]
plugins
插件:plugins 选项用于以各种方式自定义 webpack 构建过程。webpack 附带了各种内置插件,可以通过 webpack.[plugin-name] 访问这些插件。请查看这个页面获取插件列表和对应文档,但请注意这只是其中一部分,社区中还有许多插件。比如我们做脚手架一定会用到的html-webpack-plugin,可以去看一下它的Options,就可以根据业务来做脚手架了,使用也简单,install之后引入之后1
2
3
4
5plugins: [
new HtmlWebpackPlugin({
...options
})
]
devServer
开发环境下的一个内置小型本地服务器: 基于nodejs实现的服务器,你也可以自己写一个,就像这样1
2
3
4
5
6
7
8
9
10
11const express = require('express')
const webpack = require('webpack')
const webpackDevMiddleware = require('webpack-dev-middleware')
const config = require('./webpack.config.js')
const conplier = webpack(config)
const app = express()
app.use(webpackDevMiddleware(conplier,{}))
app.listen(9090,()=>{
console.log('at 9090')
})
devServer是用来提高开发效率的,它提供了一些配置项,可以用于改变devServer的默认行为,要配置devServer,除了可以在配置文件里通过devServer传入参数,还可以通过命令行传入参数。
⚠️注意!!!只有在通过devServer启动webpack时,配置文件里的devServer才会生效,因为这些参数所对应的功能都是devServer提供的,webpack本身并不认识devServer的配置项。
hot
hot配置是否启用模块的热替换功能,开启之后代码变动会自动刷新页面,做到实时热更新
两种配置方法:
1、webpack.config.js里头的devServer设置hot为true或者false,需要引入一个热更新插件1
2
3plugins: [
new webpack.HotModuleReplacementPlugin(),
],
2、命令行,在package.json中的script中,比如原本用来启动本地项目的命令后面加上–hot1
"start": "NODE_ENV=development webpack-dev-server --config webpack.develop.config.js"
1 | "start": "NODE_ENV=development webpack-dev-server --config webpack.develop.config.js --hot" |
host
devServer服务监听的地址,如果想让局域网内的其他用户访问本项目,可以将host配置为本机的IP,通过命令行 –host 0.0.0.0也可以
port
端口:如8080,9090
open
是否打开默认浏览器,如果配置了open,在run start之后会打开默认浏览器,也可以在命令行 –open
contentBase
配置devServer,Http服务器的文件根目录
proxy配置代理,处理本地跨域,
pathRewrite
这个的应用场景是,比如项目里面写了个a.json,突然接口有问题需要换到测试接口b.json,就用这个代理1
2
3
4
5
6
7
8
9
10
11
12
13
14
15devServer: {
contentBase: './dist',
open: true,
port: 8090,
hot: true,
historyApiFallback: true,
proxy: {
'/react/api': {
target: 'http://hhardyy.com',
pathRewrite: {
'a.json': 'b.json'
}
}
}
},
devServer.historyApiFallback, 解决单页面应用无法跳转的问题,比如Route 的path=/list ,localhost:8080/list打不开
HotModuleReplacementPlugin
这个插件css修改了也不会重新渲染刷新整个页面,只是把css即时修改1
2
3
4const webpack= require('webpack')
plugins:[
new webpack.HotModuleReplacementPlugin()
]
两种配置bable的方案
一
1 | { |
二
1 | "plugins": [ |
three shaking
顾名思义是摇树的意思:将一些模块中引入不用的方法摇掉,只支持import使用,因为import的底层是静态引入的方式,commonjs是动态引入的方式,开发环境(mode:development)使用three shaking要加上1
2
3optimization:{
usedExports: true //哪些模块被使用了再做打包
}
package.json加上1
"sideEffects":false
假如在某个js文件内import “@babel/polly-fill”,那three
shaking发现它没有导出模块,在打包的时候会被忽略掉,但是需要使用里头的东西,为了避免打包错误,需要添加这个配置1
"sideEffects":["@babel/polly-fill"]
false的意思是对所有的模块都进行three shaking,没有特殊处理的模块.一般如果在模块中导入了css文件,three shaking会去检测有没有模块导出,所以一般sideEffects会配置[“*.css”]
在开发环境下three shaking不会去删除没有用到的模块,因为在开发环境需要调试,如果打包上线的话three shaking其实自动就配置好了,甚至都不需要写three shaking配置。
HMR模块热替换
1 | new webpack.HotModuleReplacementPlugin() //启用 webpack 内置的 HMR插件 |
性能优化
code splitting
代码分割
同步代码:1
2
3
4
5optimization: {
splitChunks: {
chunks: 'all'
}
}
异步代码:1
npm install --save-dev @babel/plugin-syntax-dynamic-import
.babelrc1
plugins:["dynamic-import-webpack"]
index.js1
2
3
4
5
6
7
8
9
10
11
12
13function getComponent() {
return import(/*webpackChunkName:"lodash"*/'lodash').then(({
default: _
}) => {
let element = document.createElement('div')
element.innerHTML = _.join([1, 2, 3, 4, 5, 5, 5, 5, 5], '***')
return element
})
}
getComponent().then(element => {
document.body.appendChild(element)
})
webapck分析工具
git仓库1
--profile --json > stats.json //把打包过程的一些解释放到一个json文件里
打包分析: https://webpack.js.org/guides/code-splitting/#bundle-analysis
优化
在首屏主要代码加载完毕然后释放出网络之后再去加载模态框等其他代码用Prefetching / Preloading modules,
前端性能优化的时候,缓存其实不是最主要的点,而最主要的点应该是放到代码覆盖率上
css代码分割
可以看一下这里1
2
3
4
5
6
7output: {
filename: '[name].js',
chunkFilename: '[name].chunk.js', //加上这个,然后用上下面的插件
path: path.resolve(__dirname, '../dist')
}
plugins : MiniCssExtractPlugin
例如在index.js里面import 1.css和import 2.css,跑起来的时候就会将1.css和2.css合并到main.css里面,
只能用在生产环境,因为不支持HMR,用在开发环境的话会影响开发效率。
css代码压缩
用optimize-css-assets-webpack-plugin这个插件1
npm install optimize-css-assets-webpack-plugin -D
使用方法:1
2
3
4const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
optimization: {
minimizer: [new OptimizeCSSAssetsPlugin({})],
},
假如要把index.js和index1.js和indexn.js中引用的css文件单独分割到styles文件里面1
2
3
4
5
6
7
8
9
10
11
12optimization:{
splitChunks: {
cacheGroups: {
styles: {
name: 'styles',
test: /\.css$/,
chunks: 'all',
enforce: true, //忽略掉所有默认配置
},
},
},
}
performance:false //不让提示性能上的问题
缓存
每次webpack打包的文件放到服务器,第二次请求服务器会默认请求缓存1
2
3
4output: {
filename: '[name].[contenthash].js',
chunkFilename: '[name].[contenthash].js',
}
这么配置之后如果源代码没有改变,那么打包生成的cantenthash永远不变,根据content产生的hash字符串,老版本webpack如果发现没改变代码但是打包的hash值不一样,这时候需要做额外配置1
2
3
4
5optimization: {
runtimeChunk: {
name: 'runtime'
},
}
在用webpack打包的时候,main.js放的是业务逻辑,vendors放的是用的库,比如jquery,然后main.js和vendors其实是有关联的,处理这些关联的内置代码是manifest,默认存在两个文件里面,它在每次打包的时候在旧版有差异,正是这些差异导致在打包的时候虽然没有改动源代码,但是两个文件里面的manifest实际上已经跟着变了。
shimming的作用
比如在index.js文件里1
2import $ from 'jquery'
import { ui } from 'jquery.ui.js'
其中jquery.ui.js1
2
3export function ui(){
$('body').css('background','red')
}
打包之后会报错说$未定义,因为每个模块只能使用模块内部的代码,那如果有一个库,又想用又不能修改内部的代码,就可以利用webpack自带的api1
2
3
4
5
6import webpack from 'webpack'
plugins:[
new webpack.ProviderPlugin({
$:jquery
})
]
意思就是模块内发现使用$的时候,就自动引入jquery
模块this
如果想让加载的模块this指向window而不是自己(默认指向自己)1
npm install imports-loader --save-dev
然后再在test 的.js文件使用loader1
2
3
4
5
6
7
8
9
10test:/\.js$/,
exclude:/node_modules/,
use:[
{
loader:'babel-loader'
},
{
loader:'imports-loader?this=>window'
}
]
全局变量
如何在webpack打包的过程中使用全局变量
分两个环境
webpack.prod.js && webpack.dev.js && webpack.comm.js(公共配置)
1、module.exports = prodConfig
2、module.exports = devConfig1
2
3
4
5
6
7
8
9
10
11let merge = require('webpack-merge')
let commConfig = {
...公共webpack配置
}
module.exports = (env)=>{
if( env && env.production === 'hardy' ){
return merge(commConfig, prodConfig)
}else{
return merge(commConfig, devConfig)
}
}
env是package.json传入的全局变量,例如package.json里头的打包命令设置为1
2"dev":"webpack-dev-server --config ./build/webpack.comm.js"
"build" : "webpack --env.production==='hardy' --config ./build/webpack.comm.js"
如何开发一个库
1 | output: { |
libraryTarget: ‘umd’ =>
import library from ‘library.js’ ES6
const library = require(‘library’) Common
require([‘library’,function()]) AMD
library:’library’ =>支持script引入方式 意思就是打包生成的js文件挂载到页面的全局变量中然后直接library.调用里头的方法
externals
可以是字符串,数组,对象1
externals: ['lodash']
防止用户在import 再打包的时候项目中有两个lodash,不过打包之后没有lodash,需要自己引入
,就是当lodash被commonjs的方式引入的时候,名字必须是lodash,也就是const lodash = require(‘lodash’),而不能写成const _ = require(‘lodash’)1
2
3
4
5externals: {
lodash: {
commonjs:'lodash'
}
},
root的作用是当以script src的方式引入的时候页面上必须注册一个名为_的全局变量1
2
3
4
5
6externals: {
lodash: {
root: '_',
commonjs: 'lodash'
}
},
PWA
Progressive Web Application === PWA =>遇到断网的情况下依然可以访问,用户体验更好1
npm install workbox-webpack-plugin --save-dev
1 | const workboxWebpackPlugin = require('"workbox-webpack-plugin"') |
ts打包配置
打包ts文件的时候,需要在项目根目录下创建一个名为tsconfig.json的文件1
npm install @types/lodash --save-dev
eslint
eslint1
2npm install eslint --save-dev
npx eslint --init
检测src下面的代码符不符合规范要求1
npx eslint src
babel-eslint是较常用的eslint解析器,为了避免团队成员使用不同的编辑器导致的eslint检测区别,可以使用eslint-loader1
npm install eslint-loader --save-dev
1 | module:{ |
加完之后可以在webpack.config.js的devServer加入1
overlay: true
这样npm run dev的时候一旦出现eslint的问题,就会在浏览器上弹窗出打包遇到的问题
webpack性能优化
提升打包速度
跟上技术的迭代 升级webpack,npm|yarn|node
尽可能少的模块使用loader
将this指向window的配置
test:/.js$/,
exclude:/node_module/,
use:[“import-loader?this=>window”]
Plugin尽可能精简并确保可靠
插件DellPlugin提高打包速率
每次打包的时候比如react,react-dom.lodash的代码都是不会变的,所以只在第一次打包的时候去分析,理想的打包状态1
2
3
4
5
6
7
8
9
10
11const path = require('path');
module.exports = {
entry: {
vendors: ['react', 'react-dom', 'lodash']
},
output: {
filename: '[name].dell.js',
path: path.resolve(__dirname, '../dell')
}
}
单独生成的dell下面的vendors.dell.js这么来用1
npm install add-asset-html-webpack-plugin --save
这个插件是往html里面再去增加静态资源1
2
3new AddAssetWebpackPlugin({
filepath: path.resolve(__dirname, '../dell/vendors.dell.js'),
}),
分析打包文件,然后生成打包的映射1
2
3
4entry: {
vendors: ['lodash'],
react: ['react', 'react-dom'],
},
DllPlugin生成映射1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19new webpack.DllPlugin({
name: '[name]',
path: path.resolve(__dirname, '../dell/[name].manifast.json'),
}),
```
然后配置AddAssetWebpackPlugin,将它加入到html中
```javascript
new AddAssetWebpackPlugin({
filepath: path.resolve(__dirname, '../dell/vendors.dell.js'),
}),
new AddAssetWebpackPlugin({
filepath: path.resolve(__dirname, '../dell/react.dell.js'),
}),
new webpack.DllReferencePlugin({
manifest: path.resolve(__dirname, '../dell/vendors.manifast.json'),
}),
new webpack.DllReferencePlugin({
manifest: path.resolve(__dirname, '../dell/react.manifast.json'),
}),
这么配置之后,它会去dell/vendors.manifast.json中查找第三方模块的映射关系,如果能找到就不去打包,直接从vendors.dell.js引入(从全局变量里面拿),当需要分解的文件太多的时候,为了避免每个都要复制,可以用fs来动态加载1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18const files = fs.readdirSync(path.resolve(__dirname, '../dell'));
const plugins = [
new HTMLWebpackPlugin({
template: './src/index.html',
}),
];
files.forEach((file) => {
if (/.*\.dell.js/.test(file)) {
plugins.push(new AddAssetWebpackPlugin({
filepath: path.resolve(__dirname, '../dell', file),
}));
}
if (/.*\.manifast.json/.test(file)) {
plugins.push(new webpack.DllReferencePlugin({
manifest: path.resolve(__dirname, '../dell', file),
}));
}
});
DellPlugin的打包逻辑就是项目中引入比如jquery,lodash等不会改变的静态资源的时候,就直接根据entry设置的文件名打包到dell目录下然后生成映射,后面每次打包就可以直接去dell目录下加载相关资源,不需要重新打包
控制包大小
thread-loader,parallel-webpack,happypack多进程打包(利用node里面的多进程,使用多个cpu)
合理使用sourceMap
综合stats分析打包结果
开发环境内存编译
打包多页面,
一般都是单页面,也就是只有一个html的页面,要打包多个页面的时候,在entry里头写两个入口1
2
3
4entry:{
main:'./src/index.js',
list:'./src/list.js'
}
然后可以去github上查找html-webpack-plugin的配置项,或者往上翻1
2
3
4
5
6
7
8
9
10
11new HTMLWebpackPlugin({
template: './src/index.html',
filename: 'index.html',
chunks: ['runtime', 'vendors', 'main'],
}),
new HTMLWebpackPlugin({
template: './src/index.html',
filename: 'list.html',
chunks: ['runtime', 'vendors', 'list'],
}),
...
如果觉得每次这样复制不太友好,可以用封装一个函数来解决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
34const makePlagins = (configs) => {
const plugins = [
// new CleanWebpackPlugin(['dist'], {
// root: path.resolve(__dirname, '../'),
// }),
];
const keys = Object.keys(configs.entry);
keys.forEach((item) => {
plugins.push(
new HTMLWebpackPlugin({
template: './src/index.html',
filename: `${item}.html`,
chunks: ['runtime', 'vendors', item],
}),
);
});
const files = fs.readdirSync(path.resolve(__dirname, '../dell'));
files.forEach((file) => {
if (/.*\.dell.js/.test(file)) {
plugins.push(new AddAssetWebpackPlugin({
filepath: path.resolve(__dirname, '../dell', file),
}));
}
if (/.*\.manifast.json/.test(file)) {
plugins.push(new webpack.DllReferencePlugin({
manifest: path.resolve(__dirname, '../dell', file),
}));
}
});
return plugins;
};
在这个操作之前先将1
2
3
4
5
6
7
8
9
10
11module.exports={
entry:{
....
},
output:{
...
},
plugins:{
...
}
}
改成1
2
3
4
5
6
7
8const configs={
entry:{
....
},
output:{
...
}
}
configs.plugins = makePlagins(configs)
这样就可以实现动态改变打包的入口文件就可以生成新的一个页面
如何编写一个loader
webpack API
写一个loader来做异常捕获1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16const loaderUtils = require('loader-utils')
module.exports = function (source) {
//异常捕获start
try(function(){
}).catch(e){}
//异常捕获end
const options = loaderUtils.getOptions(this)
const callback = this.async()
setTimeout(() => {
const result = source.replace('hardy', options.name)
callback(null, result)
}, 1000);
}
也可以用来写国际化
比如业务代码index.js中
console.log(‘webpack4.x‘)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17loader里头这么写
const loaderUtils = require('loader-utils')
module.exports = function (source) {
if (node全局变量 === '中文') {
source.replace('{{title}}', '中文标题')
} else {
source.replace('{{title}}','english title')
}
const options = loaderUtils.getOptions(this)
const callback = this.async()
setTimeout(() => {
const result = source.replace('hardy', options.name)
callback(null, result)
}, 1000);
}
编写一个plugin
1 | "scripts": { |
最近拿了新offer,今天也是国庆,祖国70周年的生日!!!
分享一张觉得拍的不错的美图。