webpack打包详解(页面性能优化)
先附上webpack的中文文档地址:https://www.webpackjs.com/
先让我们来了解一下什么是webpack
webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle,在webpack眼里一切都可以打包,打包成js文件
一.下载webpack环境
npm i webpack@4 webpack-cli@3 -D
安装webpack4 配置 cli3
接下来做一个小测试
在src下创建app.js
src/app.js
import name from './name.js'
console.log(name)
src/name.js
export default 'test'
命令行输入打包指令
webpack --entry ./src/app.js --output dist/bundle.js
//--entry后为入口文件地址 --output后为出口文件地址
// 可以进入dist文件夹中查看是否打包成功
二.正式配置
第一步运行完毕,成功打包,说明了webpack的环境已经安装成功,接下来就可以开始正式的打包配置了
2.1 入口及出口的配置
src/app.js
console.log(config)
//app.js 入口文件中打印一个字符
根目录下创建config/webpack.config.dev.js
const path = require('path')
module.exports = {
mode: 'development',
//入口,从入口开始逐个模块打包
entry: {
app: path.resolve(__dirname, '../src/app.js')
},
//出口,编译完成输出文件夹
output: {
path: path.resolve(__dirname, '../dist'),
filename: '[name].js' //[name] 拿到entry中的key值
}
}
配置启动命令
"scripts": {
"dev": "",
"build": "webpack --config ./config/webpack.config.dev.js"
}
2.2. 载入其他功能插件
npm i html-webpack-plugin -D/ yarn add html-webpack-plugin -D
解析html
npm i copy-webpack-plugin -D/ yarn add copy-webpack-plugin -D
打包小图标
npm i clean-webpack-plugin -D/ yarn add clean-webpack-plugin -D
删除dist中的文件
解析html:导入html-webpack-plugin
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
...,
//插件
plugins: [
new HtmlWebpackPlugin({
//导入文件名
filename: 'index.html',
//模板:导入的html路径
template: path.resolve(__dirname, '../public/index.html')
})
]
}
打包小图标:导入copy-webpack-plugin
const CopyWebpackPlugin = require('copy-webpack-plugin')
module.exports = {
...,
plugins: [
...,
new CopyWebpackPlugin({
patterns: [
{
from: resolve('public/favicon.ico'),
to: resolve('dist/')
}
]
})
]
}
删除dist图片:npm i clean-webpack-plugin
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
...,
plugins: [
new CleanWebpackPlugin (),
...
]
}
2.3. 样式处理
打包css也是css代码压缩,简单来说就是无效代码删除和css语义合并css的样式文件可分为css、stylus、scss,打包不同类型的文件,也需要下载对应的模块
css:npm i css-loader -D / yarn add css-loader -D
stylus:npm i stylus stylus-loader -D / yarn add stylus stylus-loader -D
scss:npm i node-sass sass-loader -D / yarn add ndoe-sass sass-loader -D
style-loader:npm i style-loader -D / yarn add style-loader -D
css抽离:npm i mini-css-extract-plugin -D / yarn add mini-css-extract-plugin -D
2.3.1. css:css类型文件的打包
module.exports = {
...,
//模块
module: {
rules: [
...,
{
//正则验证,.css为后缀的使用css-loader
test: /\.css$/,
//载入程序:
loader: 'css-loader'
}
]
},
...
}
若样式导入进js中,将其解析出来需要使用style-loader 模块
npm i style-loader -D / yarn add style-loader -D
module.exports = {
...,
module: {
rules: [
...,
{
test: /\.css$/,
// loader: 'css-loader'
loaders: ['style-loader', 'css-loader'] // 后面的模块为前面的服务
}
]
},
...
}
2.3.2. stylus:stylus类型的打包
module.exports = {
...,
module: {
rules: [
...,
{
test: /\.(css|styl)$/,
// loader: 'css-loader'
loaders: ['style-loader', 'css-loader', 'stylus-loader'] // 后面的为前面的服务
}
]
},
...
}
2.3.3. scss:scss类型的打包
module.exports = {
...,
module: {
rules: [
...,
{
test: /\.scss$/,
loaders: ['style-loader', 'css-loader', 'sass-loader'] // 后面的为前面的服务
}
]
},
...
}
2.3.4. css的抽离:css代码被css-loader转换后,交给的是style-loader进行处理。
style-loader使用的方式是用一段js代码,将样式加入到style元素中。而实际的开发中,我们往往希望依赖的样式最终形成一个css文件
此时,就需要用到一个库:mini-css-extract-plugin
,该库提供了1个plugin和1个loader
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
...,
module: {
rules: [
...,
{
test: /\.(css|styl)$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'stylus-loader']
},
{
test: /\.scss$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader']
}
]
},
plugins: [
...,
new MiniCssExtractPlugin({
filename: 'style/[name].css'
})
],
...
}
2.3.5. 自动补全css:
对于css属性,尤其对css3的新属性而言,浏览器私有属性前缀,是非常滴重要,然鹅,如果要知道每个属性的浏览器私有前缀,那是非常的麻烦,“如何补齐css前缀”,且看**autoprefixer
//配置文件中
const loaderUse = (fileLoader) => {
return [
{
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: '../'
}
},
'css-loader',
'postcss-loader', // ******
fileLoader
]
}
//posts.config.js
module.exports = {
plugins: [
require('autoprefixer')({
overrideBrowserslist: ['last 100 versions']
})
]
}
//如果不设置配置文件
const loaderUse = (fileLoader) => {
return [
{
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: '../'
}
},
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
[
'autoprefixer',
{
overrideBrowserslist: ['last 100 versions']
}
],
[
'postcss-preset-env',
{
// Options
}
]
]
}
}
},
fileLoader
]
}
2.4. 处理图片
方式一:src/assets图片
module.exports = {
...,
module: {
rules: [
...,
{
test: /\.(jpg|png|jpeg|gif)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 2048 // 如果设置的值够大,显示base64(内存中),如果设置的值小,显示图片地址
}
}
]
}
]
},
...
}
如果需要设置图片的输出目录
module.exports = {
...,
module: {
rules: [
...,
{
test: /\.(jpg|png|jpeg|gif)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 2048, // 如果设置的值够大,显示base64,如果设置的值小,显示图片地址
outputPath: '../dist/images' // 不要写绝对路径 类似 path.resolve()ß
}
}
]
}
]
},
...
}
方式二:放入public 拷贝至 dist
不受webpack管辖,直接拷贝即可,使用copy-webpack-plugin插件
module.exports = {
...,
plugins: [
...,
new CopyWebpackPlugin({
patterns: [
{
context: resolve('public/'), // 一定要添加上下文对象,否则直接复制public目录至dist
from: '**/*',
to: resolve('dist/'),
globOptions: {
ignore: ['index.html']
}
}
]
}),
...
],
...
}
2.5. 处理高级js
npm i @babel/core @babel/preset-env babel-loader -D / yarn add @babel/core @babel/preset-env babel-loader -D
@babel/preset-env是一系列插件的集合,包含了我们在babel6中常用的es2015,es2016, es2017等最新的语法转化插件,允许我们使用最新的js语法,比如 let,const,箭头函数等等,但不包括stage-x阶段的插件。
module.exports = {
...,
module: {
rules: [
{
test: /\.js$/,
// loader: 'babel-loader '
// bower_components 是以前的一种包管理器 bootstrap
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
},
...
]
},
...
}
但是如果给类添加属性之后,就会发生错误,此时需要添加新的插件
npm i @babel/plugin-proposal-class-properties -D / yarn add @babel/plugin-proposal-class-properties -D
module.exports = {
...,
module: {
rules: [
{
test: /\.js$/,
// loader: 'babel-loader '
// bower_components 是以前的一种包管理器 bootstrap
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
plugins: ['@babel/plugin-proposal-class-properties']
}
}
},
...
]
},
...
}
如果代码包含 async await,需要添加新的插件
npm i @babel/plugin-transform-runtime @babel/runtime -D / yarn add @babel/plugin-transform-runtime @babel/runtime -D
module.exports = {
...,
module: {
rules: [
{
test: /\.js$/,
// loader: 'babel-loader '
// bower_components 是以前的一种包管理器 bootstrap
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
plugins: [
'@babel/plugin-proposal-class-properties',
'@babel/plugin-transform-runtime'
]
}
}
},
...
]
},
...
}
如果代码中有装饰器,同样需要添加新的插件
npm i @babel/plugin-proposal-decorators -D / yarn add @babel/plugin-proposal-decorators -D
module.exports = {
...,
module: {
rules: [
{
test: /\.js$/,
// loader: 'babel-loader '
// bower_components 是以前的一种包管理器 bootstrap
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
plugins: [
'@babel/plugin-transform-runtime', // 处理async await
[
'@babel/plugin-proposal-decorators',
{
legacy: true
}
],
[
'@babel/plugin-proposal-class-properties', // 处理类的属性
{
loose: true
}
]
]
}
}
},
...
]
},
...
}
如果js中含有浏览器解析不了的语句,可以使用垫片
npm i @babel/polyfill -D / yarn add @babel/polyfill -D
方式一:入口文件顶级添加如下语句
import "@babel/polyfill";
方式二:配置文件入口处
module.exports = {
entry: {
app: ["@babel/polyfill", path.resolve(__dirname, '../src/app.js')]
}
}
方式三:添加@babel/preset-env时添加配置选项
module.exports = {
...,
module: {
rules: [
{
test: /\.js$/,
// loader: 'babel-loader '
// bower_components 是以前的一种包管理器 bootstrap
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage'
}
]
],
plugins: [
'@babel/plugin-transform-runtime', // 处理async await
[
'@babel/plugin-proposal-decorators',
{
legacy: true
}
],
[
'@babel/plugin-proposal-class-properties', // 处理类的属性
{
loose: true
}
]
]
}
}
},
...
]
},
...
}
此时运行查看会有提示信息安装core-js模块,需要安装配置
npm i core-js@3 -D / yarn add core-js@3 -D
module.exports = {
...,
module: {
rules: [
{
test: /\.js$/,
// loader: 'babel-loader '
// bower_components 是以前的一种包管理器 bootstrap
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs:3
}
]
],
plugins: [
'@babel/plugin-transform-runtime', // 处理async await
[
'@babel/plugin-proposal-decorators',
{
legacy: true
}
],
[
'@babel/plugin-proposal-class-properties', // 处理类的属性
{
loose: true
}
]
]
}
}
},
...
]
},
...
}
为什么要使用垫片
Babel默认只转换新的JavaScript句法(syntax),而不转换新的API,比如 Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise等全局对象,以及一些定义在全局对象上的方法(比如Object.assign)都不会转码。举个例子,ES6在Array对象上新增了Array.from方法。Babel就不会转码这个方法。如果想让这个方法运行,必须使用babel-polyfill,为当前环境提供一个垫片。