1 简介

1.1 webpack 五个核心概念

1.1.1 Entry

入口,指示webpack以哪个文件为入口起点开始打包,分析构建内部依赖图

1.1.2 Output

输出,指示webpack打包后的资源bundles输出到哪里去,以及怎么命名

1.1.3 Loader

加载器,可以让webpack能够去处理非js文件(webpack自身之理解javascript)

1.1.4 Plugins

插件,可以用于执行更广范围的任务,插件的范围包括从打包优化和压缩一直到重新定义环境变量等

1.1.5 Mode

模式,指示webpack使用相应模式的配置

  • development:能让代码本地调试运行的环境
  • production:能让代码优化上线的运行环境

2 webpack 初体验

2.1 创建一个webpack项目

  1. 创建一个目录,初始化包管理 npm init
  2. 运行命令npm install webpack webpack-cli -D
  3. 新建src文件夹用于存放代码,新建build文件夹用于存放打包好的文件
  4. 编辑代码
  5. 运行命令(开发环境) webpack ./src/index.js -o ./build/build.js --mode=development

默认只能处理js/json文件

2.2 打包css样式资源

  1. 新建css文件style.css

  2. 在index.js中引入 import 'style.css'

  3. 创建webpack.config.js文件来配置webpack(使用commentJS)

    const {
    resolve
    } = require('path');
    module.exports = {
    mode: 'development', // 模式,不能同时存在
    // mode: 'production'
    entry: './src/index.js', // 入口文件
    output: {
    filename: 'built.js', // 输出文件名
    path: resolve(__dirname, 'build') // 输出路径
    },
    module: { // loader配置
    rules: [{
    test: /\.css$/,// css结尾的文件
    use: [// 执行顺序:从右到左,从下到上,依次执行
    'style-loader',// 创建爱你style标签,将js中的样式资源插入,添加到head
    'css-loader',// 将css文件变成commentjs模块加载js中,内容是字符串
    ]
    }]
    },
    plugins: [ // 插件配置
    ],
    }
  4. 安装所需要的loader

  5. 运行命令 webpack

使用less、scss等预编译语法需要下载对应的loader,配置方式同上,再添加一个rule对象

2.3 打包HTML文件

  1. 安装打包html的插件 npm install html-webpack-plugin-D

  2. 在webpack.config.js中引入并添加配置

    const HtmlWebpackPlugin = require('html-webpack-plugin');
    ···
    plugins: [ // 插件配置
    // html-webpack-plugin
    // 功能: 创建一个空的html文件引入打包之后的所有资源
    new HtmlWebpackPlugin({
    // 赋值目标文件并引入打包的资源
    template: './src/index.html'
    })
    ],

在创建html模板的时候不要引入资源

2.4 打包图片文件

图片添加略过,只给出配置方式

module: { // loader配置
rules: [{
test: /\.(jpg|png|gif|jpeg)$/,
// 只有一个loader的时候可以直接loader+loader名
loader: 'url-loader',// 下载时需要下载url-loader和file-loader
options: {
/**
* 限制大小,小于限制就会被base64处理
* 优点减少请求数量,减轻服务器压力
* 缺点图片会更大,文件请求速度会变慢
*/
limit: 200 * 1024
}
}]
},

如果html模板中添加了图片,添加一个额外的loader来解析

{
test: /\.html$/,
loader: 'html-withimg-loader' // 处理html中的图片
}

但是,因为url-loader默认使用es6模块化解析,而html-loader引入图片是commentjs

解析时会出现[object Module]

解决办法:关闭ur-loader的ES6解析,在url-loader的options中添加 esModule: false

2.5 打包其他资源

{
exclude: /\.(css|html|jpg|png|gif|jpeg)$/,
loader: 'file-loader'
}

除了这匹配到的后缀文件之外,其他的文件用file-loader打包

2.6 使用devServer

开发服务器:用来自动编译,热更新等

在内存中编译打包,不会有任何输出

运行:webpack-dev-server

  1. 运行npm install webpack-dev-server -D安装

  2. webpack.config.js中添加配置

    devServer: {
    contentBase: resolve(__dirname, 'build'),// 项目构建后的路径
    compress: true,// 启动gzip压缩
    port: 3000,// 端口号
    open: true,// 打开默认浏览器
    }
  3. 运行webpack-dev-server

3 开发环境配置

const {
resolve
} = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'development',
// mode: 'production'
entry: './src/index.js',
output: {
filename: 'built.js',
path: resolve(__dirname, 'build')
},
module: {
rules: [{
test: /\.css$/,
use: [
'style-loader',
'css-loader',
]
},{
test: /\.(jpg|png|gif|jpeg)$/,
loader: 'url-loader',
options: {
limit: 200 * 1024,
esModule: false
}
},{
test: /\.html$/,
loader: 'html-loader'
}]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
})
],
devServer: {
contentBase: resolve(__dirname, 'build'),
compress: true,
port: 3000,
open: true,
}
}

4 生产环境搭建

4.1 提取css成单独文件

  1. 安装插件 npm install mini-css-extract-plugin -D

  2. 修改webpack.config.js配置文件

    const MiniCssExtractPlugin = require('mini-css-extract-plugin');
    ···
    module: {
    rules: [{
    test: /\.css$/,
    use: [
    // 'style-loader',
    MiniCssExtractPlugin.loader,// 替换style-loader,提取css成单独文件
    'css-loader',
    ]
    },
    ···]
    },
    plugins: [
    ···
    new MiniCssExtractPlugin({
    filename: 'css/style.css'
    })
    ],
  3. 运行打包命令

4.2 css兼容性处理

使用到postcss

  1. 运行 npm install postcss-loader postcss-preset-env -D

  2. 修改webpack.config.js的css相关部分配置

    {
    test: /\.css$/,
    use: [
    MiniCssExtractPlugin.loader,
    'css-loader',
    {
    loader: 'postcss-loader',
    options: {
    ident: 'postcss',
    plugins: () => [
    require('postcss-preset-env')()
    ]
    }
    }
    ]
    }
  3. 在package.json中添加浏览器版本控制

    "browerslist": {
    "development": [
    "last 1 chrome version",
    "last 1 firefox version",
    "last 1 safair version"
    ],
    "production": [
    ">0.2%",
    "not dead",
    "not op_mini all"
    ]
    }
  4. 运行打包

4.3 压缩css

使用插件 optimize-css-assets-webpack-plugin

  1. 运行npm install optimize-css-assets-webpack-plugin -D

  2. webpack.config.js中引入

    const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
    ···
    plugins: [
    ···
    new OptimizeCssAssetsWebpackPlugin()
    ]

4.4 js语法检查

使用eslint,使用airbnb规则检查js语法

  1. 运行 npm install eslint-loader eslint eslint-config-airbnb-base eslint-plugin-import -D 安装所需的依赖和插件

  2. 在package.json中设置eslintConfig,继承airbnb

    "eslintConfig": {
    "extends": "airbnb-base"
    }
  3. webpack.config.js中添加rules

    {
    test: /\.js$/,
    exclude: /node_modules/,// 只检查自己的代码,不检查第三方库中的代码
    loader: 'eslint-loader',
    options: {
    fix: true// 自动修复eslint错误
    }
    }
  4. 使用 // eslint-disable-next-line控制下一行不进行检查

4.5 js兼容性处理

使用babel-loader进行兼容性处理

  1. 运行 npm install babel-loader @babel/core @babel/preset-env @babel/polyfill -D

  2. 添加webpack.config.js的rules

    {
    test: /\.js$/,
    exclude: /node_modules/,
    loader: 'babel-loader',
    options: {
    presets: ['@babel/preset-env']
    }
    },

@babel/polyfill属于高级语法支持,在需要兼容的js文件中引入即可

import ‘@babel/polyfill’

@babel/polyfill属于暴力支持,回事打包文件变的非常大,所以一般采用按需加载的方式:core.js

  1. 运行 npm install corejs -D

  2. 修改之前的兼容性配置规则

    {
    test: /\.js$/,
    exclude: /node_modules/,
    loader: 'babel-loader',
    options: {
    presets: [
    [
    '@babel/preset-env',
    {
    useBuiltIns: 'usage',// 按需加载
    corejs: {
    version: 3
    },
    targets: {// 制定兼容浏览器版本
    chrome: '60',
    firefox: '50',
    ie: '9',
    safari: '10'
    }
    }
    ]
    ]
    }
    },

4.6 HTML和js压缩

4.6.1 js压缩

将模式调整为production即可实现js代码压缩

4.6.2 HTML压缩

在html的插件中添加设置

new HtmlWebpackPlugin({
// 复制目标文件并引入打包的资源
template: './src/index.html',
minify: {
// 移除空格
collapseWhitespace: true,
// 移除注释
removeComments: true
}
}),

5 生产环境配置

const {
resolve
} = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
filename: 'js/built.js',
path: resolve(__dirname, 'build')
},
module: { // loader配置
rules: [{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: () => [
require('postcss-preset-env')()
]
}
}
]
}, {
test: /\.(jpg|png|gif|jpeg)$/,
loader: 'url-loader',
options: {
limit: 200 * 1024,
esModule: false
}
}, {
test: /\.html$/,
loader: 'html-loader'
},
{
test: /\.js$/,
exclude: /node_modules/,
enforce: 'pre',// 优先执行
loader: 'eslint-loader',
options: {
fix: true
}
}, {
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: {
version: 3
},
targets: {
chrome: '60',
firefox: '50',
ie: '9',
safari: '10'
}
}
]
]
}
},
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
minify: {
collapseWhitespace: true,
removeComments: true
}
}),
new MiniCssExtractPlugin({
filename: 'css/style.css'
}),
new OptimizeCssAssetsWebpackPlugin()
],
devServer: {
contentBase: resolve(__dirname, 'build'),
compress: true,
port: 3000,
open: true,
},
// mode: 'development',
mode: 'production',
}

babel和eslint可能会产生冲突使用enforce: ‘pre’让eslint先执行

6 性能优化篇

6.1 开发环境性能优化

6.1.1 优化打包构建速度

6.1.1.1HMR——hot module replacement(模块热替换)

只会重新打包这一个模块,不会重新打包

配置devServer

devServer: {
contentBase: resolve(__dirname, 'build'),
compress: true, // 启动gzip压缩
port: 3000, // 端口号
open: true, // 打开默认浏览器
hot: true,// 开启热更新
},

样式文件热更新基于’style-loader’;

js文件默认不能使用HMR功能;

HTML文件默认不能使用HMR功能,同时会导致问题,HTML文件不能热更新,解决办法:修改entry入口,将html文件引入(不需要做HMR)

6.1.1.2 oneOf减少loader检索

每个文件在检索的的时候会每个rules都验证一遍,将所有的rules都放到oneOf中就可以验证到所在的规则不再继续验证

这样出现了问题,js可能会有多个loader,解决办法:oneOf中保留一条rule,其他的取出来与oneOf平级

6.1.2 优化代码调试

source-map:提供语言代码构建后代码映射技术(构建代码出错,追踪源代码错误)

添加devtool

devtool: 'source-map',

[inline- |hidden- | eval-][nosources-][cheap-[module-]]source-map分别对应:

  • inline-内联:(汇集在一起)错误代码准确信息和源代码错误位置

  • hidden-外部:代码错误原因,没有错误位置,不能追踪代码错误

  • eval-内联:(分别跟每个文件对应)错误代码准确信息和源代码错误位置(位置信息是哈希值)

  • nosources-外部:错误代码准确信息,没有源代码错误信息

  • cheap-外部:错误代码准确信息和源代码错误位置(只精确到行,提示整行错误)

  • cheap-module-外部:错误代码准确信息和源代码错误位置

  • source-map`外部:错误代码准确信息和源代码错误位置:

模式使用

  • 开发环境:速度快,开发更友好

    • 速度快eval>inline>cheap>···
      • eval-cheap-source-map
      • eval-source-map
    • 调试更友好
      • source-map
      • cheap-module-source-map
      • cheap-source-map

    –>eval-source-map / eval-cheap-module-source-map

  • 生产环境:源代码要不要隐藏

    • 内联会让代码体积变大,使用外部方式
      • source-map
      • ···
    • 隐藏代码
      • nosource-source-map// 全部隐藏
      • hidden-source-map// 隐藏源代码

    –>source-map / cheap-module-source-map

6.2生产环境性能优化

缓存机制

babel缓存

修改js打包配置,开启缓存

{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage', // 按需加载
corejs: {
version: 3
},
targets: { // 制定兼容浏览器版本
chrome: '60',
firefox: '50',
ie: '9',
safari: '10'
}
}
]
],
cacheDirectory: true// 开启缓存
}
},

强缓存模式,所有的数据都要缓存,从新打包之后不会试用新的文件

文件资源缓存
hash

这里修改输出文件名,每次到打包之后的文件都不一样,这就不加绝了强制缓存带来的不刷新了(取10位hash值)

output: {
filename: 'js/built[hash:10].js', // 输出文件名
path: resolve(__dirname, 'build') // 输出路径
},

所有的js和css文件共用一个哈希值,重新打包会导致所有的缓存失效

chunkhash

根据chunk生成的hash值,如果打包源来自同一个chunk,那么hash值相同

此时,js和css的hash值还是相同,因为css是在js中引入的,同属于一个chunk

contenthash

根据文件内容生成hash,不同文件hash值一定不一样

filename: 'js/built[contenthash:10].js'

如果单独提取了css,css命名修改为

new MiniCssExtractPlugin({
filename: 'css/style-[contenthash:10].css'
})

tree shaking

去除无用代码

  1. 必须使用ES6模块化
  2. 开启production环境

打包是会自动摇去没有用的叶子

在package.json中配置"sideEffects": false所有代码都没有副作用

​ 问题:可能会把css文件干掉,解决"sideEffects": ["*.css"]

code split

文件分割

  • 多入口文件分割

  • 添加webpack.config.js配置项,将node中的代码单独打包一个chunks,自动分析多入口文件中有没有公共文件,如果有会单独打包成一个chunk

optimization: {
splitChunks: {
chunks: 'all'
}
},
  • 通过js代码,让某个文件单独打包成一个chunk

懒加载、预加载

懒加载
document.getElementById('btn').onclick = function() {
import(/* webpackChunkName: 'test'*/'./js/test.js')
.then(({mul}) => {
console.log(mul(4, 5));
})
}

将引入放在事件的回调函数中,事件触发时才加载js文件

配合eslint语法检查是有问题的,在.eslintrc文件汇总设置 "allowImportExportEverywher": true,允许在任何地方import

预加载

将引入改为

import(/* webpackChunkName: 'test', webpackPrefetch: true */'./js/test.js')

预加载会提前将js文件加载

区别
  • 懒加载:文件需要才加载
  • 预加载:使用之前加载,浏览器空闲再加载(兼容性较差)
  • 正常加载:并行加载

PWA

渐进式网络开发应用程序(离线可访问)

​ workbox–>workbox-webpack-plugin

  1. 运行 npm install workbox-webpack-plugin -D

  2. 修改webpack.config.js配置

    const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
    ···
    plugin: [
    ···
    new WorkboxWebpackPlugin.GenerateSW({
    clientsClaim: true,
    skipWaiting: true
    })
    ]

    帮助serviceworker快速启动;删除旧的serviceworker

  3. 在入口文件中注册serviceworker,处理兼容

    if('serviceWorker' in navigator) {
    window.addEventListener('load', () => {
    navigator.serviceWorker.register('./service-worker.js')
    .then(() => {
    console.log('sw注册成功')
    }).catch(() => {
    console.log('sw注册失败')
    })
    })
    }
  4. eslint不识别window,navigator等全局变量,修改package.json配置

    "eslintConfig": {
    "extends": "airbnb-base",
    "env": {
    "browser": true// 支持浏览器全局变量
    }
    }
  5. 构建代码

多进程打包

消耗时间长的才需要多进程打包,多进程开始需要时间,进程通信也需要时间

  1. 运行npm install thread-loader -D

  2. 修改webpack.config.js中babel所在的rule

    {
    test: /\.js$/,
    exclude: /node_modules/,
    use: [
    {
    loader: 'thread-loader',// 多进程打包
    options: {
    workers: 2,// 使用两个进程
    }
    },
    {
    loader: 'babel-loader',
    ···
    }
    ]
    },

externals

忽略某些包,比如CDN引入的

在webpack.config.js第一级目录下添加

externals: {
// 包名: npm库名
jquery: 'jQuery'
}

dll

  1. 新建一个配置文件,例如 webpack.dll.js

    const {resolve} = require('path');
    const webpack = require('webpack');
    module.exports = {
    entry: {
    // [name]: 要打包的库名
    jquery: ['jquery']
    },
    output: {
    filename: '[name].js',
    path: resolve(__dirname, 'dll'),
    library: '[name]_[hash]'// 打包库里暴露的内容叫什么名
    },
    plugins: [
    // 打包生成一个映射关系
    new webpack.DllPlugin({
    name: '[name]_[hash]',// 映射库暴露的内容名称
    path: resolve(__dirname, 'dll/manifast.json')// 输出文件路径
    })
    ],
    mode: 'production'
    }
  2. 运行命令 webpack --config webpack.dll.js

  3. 运行 npm install add-asset-html-webpack-plugin -D

  4. 修改webpack.config.js配置

    const webpack = require('webpack');
    const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
    ···
    plugins: [
    ···
    // 告诉webpack那些文件不需要打包,以及映射关系
    new webpack.DllReferencePlugin({
    manifest: resolve(__dirname, 'dll/manifest')
    }),
    // 将文件打包输出,并在html自动引入该文件
    new AddAssetHtmlWebpackPlugin({
    filepath: resolve(__dirname, 'dll/')
    })
    ]
Powered By Valine
v1.5.2

前端小白