webpack学习笔记

首先贴上自己关键插件版本

    "webpack": "^5.52.0",
    "webpack-cli": "^4.8.0",
    "webpack-dev-server": "^4.1.1"

本文引用依赖较多,请注意配置是否已失效。

webpack核心概念

本质上,webpack 是一个用于现代 JavaScript 应用程序的 静态模块打包工具。当 webpack 处理应用程序时,它会在内部从一个或多个入口点构建一个 依赖图(dependency graph),然后将你项目中所需的每一个模块组合成一个或多个 bundles,它们均为静态资源,用于展示你的内容。

在开始前你需要先理解一些核心概念

入口起点(entry point) 指示 webpack 应该使用哪个模块,来作为构建其内部 依赖图(dependency graph) 的开始。进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。

输出(output)

output 属性告诉 webpack 在哪里输出它所创建的 bundle,以及如何命名这些文件。主要输出文件的默认值是 ./dist/main.js,其他生成文件默认放置在 ./dist 文件夹中。

loader

webpack 只能理解 JavaScript 和 JSON 文件,这是 webpack 开箱可用的自带能力。loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效 模块,以供应用程序使用,以及被添加到依赖图中

在更高层面,在 webpack 的配置中,loader 有两个属性:

  1. test 属性,识别出哪些文件会被转换。
  2. use 属性,定义出在进行转换时,应该使用哪个 loader。

插件(plugin)

loader 用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。包括:打包优化,资源管理,注入环境变量。

Webpack 支持所有符合 ES5 标准 的浏览器(不支持 IE8 及以下版本)。webpack 的 import() 和 require.ensure() 需要 Promise。如果你想要支持旧版本浏览器,在使用这些表达式之前,还需要 提前加载 polyfill

环境(environment)

Webpack 5 运行于 Node.js v10.13.0+ 的版本。

process.env

process.env就是Nodejs提供的一个API,它返回一个包含用户环境信息的对象。如果我们给Nodejs 设置一个环境变量,并把它挂载在 process.env 返回的对象上,便可以在代码中进行相应的环境判断。

设置process.env

一般通过设置package.json来实现,但是Windows 系统和Mac系统有区别。

Windows 系统

// package.json
{
  ...
  "scripts": {
    "dev": "set NODE_ENV=development  webpack-dev-server",
    "build": "set NODE_ENV=production xxx"
  }
}

Mac 系统

// package.json
{
  ...
  "scripts": {
    "dev": "export NODE_ENV=development  webpack-dev-server ",
    "build": "export NODE_ENV=production xxx"
  }
}

它们的语法都相同,在相反环境会带来问题,所以有了cross-env。 cross-env是一个跨平台设置环境变量的第三方包,它可以让你只配置一行命令,就能轻松地在多个平台设置环境变量。首先先安装

npm install --save-dev cross-env 

然后

// package.json
{
  ...
  "scripts": {
    "dev": "cross-env NODE_ENV=development  webpack-dev-server ",
    "build": "cross-env NODE_ENV=production xxx"
  }
}

使用

全局下可以使用

    //webpack.config.js
    const isProd = process.env.NODE_ENV === 'production';

一、初识webpack

1、配置文件名称

webpack默认配置文件:webpack.config.js

可以通过webpack --config指定配置文件

module.exports = {
    entry: './src/index.js', // 4.0会默认制定入口位置为‘src/index.js’
    output: './dist/main.js', // 4.0会默认制定入口位置为‘dist/main.js’
    mode: 'production', // 环境
    module: {
        rules: [ // loader配置
            { 
                test:/\.txt$/, use: 'raw-loader'
            }
        ]
    },
    plugins:[
        new HtmlwebpackPlugin({ // 插件配置
            template: './src/index.html'
        })
    ]
}

2、安装nvm

安装 nvm(node.js version management,顾名思义是一个nodejs的版本管理工具。通过它可以安装和切换不同版本的nodejs。下面列出下载、安装及使用方法。)

安装命令:

curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.8/install.sh | bash

or Wget:

wget -qO- https://raw.githubusercontent.com/creationix/nvm/v0.33.8/install.sh | bash

安装完之后添加到环境变量。

source ~/.bash_profile 

// 推出并重启终端,查看是否安装成功: 
nvm --version

// 安装node.js: 
nvm i v10.15.3

// 创建项目文件夹,并初始化
mkdir 01project
cd 01project
// 所有询问都是yes
npm init -y
//安装webpack
npm i  webpack webpack-cli --save-dev
//查看项目是否安装成功
./node_modules/.bin/webpack -v
webpack 5.52.0
webpack-cli 4.8.0

3、一个简单例子

新建webpack.config.js文件

const path = require('path')

module.exports = {
    entry: './src/index.js',
    output: {
        path: path.join(__dirname, 'dist'),
        filename: 'bundle.js'
    },
    mode: 'production'
}

新建src/index.js、src/helloworld.js文件

// index.js文件
import { helloworld } from "./helloworld";
document.write(helloworld())
// helloworld.js文件
export function helloworld(){
    return 'hello webpack'
}

运行./node_modules/.bin/webpack命令,打包文件

新建dist/index.html文件,并引入打包文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script src="./bundle.js"></script>
</body>
</html>

4、通过npm script运行webpack 

为什么package.json可以直接运行node_module/.bin的命令

原理:模块局部安装会在node_module/.bin目录创建软链接

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build":"webpack"
  },

新建src/serach.js文件

document.write('search info')

修改webpack.config.js文件 //通过占位符确保文件名称唯一

const path = require('path')

module.exports = {
    entry: {
        'index': './src/index.js',
        'search': './src/search.js'
    },
    output: {
        path: path.join(__dirname, 'dist'),
        filename: '[name].js'
    },
    mode: 'production'
}

修改dist/index.html文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script src="./index.js"></script>
    <script src="./search.js"></script>
</body>
</html>

5、核心概念之Loaders

webpack开箱即用只支持JS和JSON两种文件类型,通过Loaders去支持其他文件类型并且把她们转化成有效的模块,并且可以添加到依赖图中。

本身是一个函数,接受源文件作为参数,返回转换的结果。

常用的loaders有哪些

6、核心概念之Plugins 

插件用于bundle文件的优化,资源管理和环境变量注入,作用域整个构建过程。

二、常用Loaders

2.1、解析es6、React JSX、Vue

2.1.解析es6 babel-loader(配置文件.babelrc)

安装相关依赖

npm install -D babel-loader @babel/core @babel/preset-env webpack

webpack配置

module:{
        rules: [
            { 
                test: /\.js$/,
                use: 'babel-loader'
            }
        ]
    }

为了支持es6需要增加配置

    module:{
        rules: [
            { 
                test: /\.js$/,
                use: {
                    loader: 'babel-loader',
                    option: { 
                        // babel-loader两个重要概念 
                        // presets是一系列babel-plugins的集合,
                        // 一个plugins对应一个功能
                        presets: ["@babel/preset-env"],
                        // plugins: ['@babel/plugin-proposal-object-rest-spread']
                    }
                }
            }
        ]
    }

又或者给babel一个配置文件.babelrc,新建.babelrc文件

{
    "presets": ["@babel/preset-env"]
}

2.1.2 解析React JSX  @babel/preset-react

安装相关依赖

npm install -D react-dom @babel/preset-react 

修改.babelrc文件

{
    "presets": [
        "@babel/preset-env",
        "@babel/preset-react"
    ]
}

修改src/search.js文件,编写react组件

import React from 'react'
import ReactDOM from 'react-dom'
class Search extends React.Component{
    render() {
        return <div>search components</div>
    }
}

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

2.1.3 解析Vue  vue-loader 和 vue-template-compiler

Vue Loader 的配置和其它的 loader 不太一样。除了通过一条规则将 vue-loader 应用到所有扩展名为 .vue 的文件上之外,请确保在你的 webpack 配置中添加 Vue Loader 的插件:

这个插件是必须的! 它的职责是将你定义过的其它规则复制并应用到 .vue 文件里相应语言的块。例如,如果你有一条匹配 /\.js$/ 的规则,那么它会应用到 .vue 文件里的 <script> 块。

npm install -S vue
npm install -D vue-loader vue-template-compiler
// webpack.config.js
const { VueLoaderPlugin } = require('vue-loader')

module.exports = {
  module: {
    rules: [
      // ... 其它规则
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      }
    ]
  },
  plugins: [
    // 请确保引入这个插件!
    new VueLoaderPlugin()
  ]
}
import search from './search.vue' // 正常的vue写法
//默认 webpack 无法打包 .vue文件 要安装相关的loader  cnpm i vue-loader vue-template-compiler -D
import Vue from 'vue'
var vm = new Vue({
    el: '#app',
    data: {
        msg: '123'
    },
    render:  (createElements, context) => createElements(search)
   
})

2.2 css解析相关 css-loader、style-loader

2.2.1css-loader、style-loader(css-loader解析css、style-loader将样式通过<style>标签插入到head中)

安装依赖

npm i -D css-loader style-loader

 修改webpack.config.js文件

 module:{
        rules: [
            { 
                test: /\.js$/,
                use: {
                    loader: 'babel-loader',
                }
            },
            { 
                test: /\.css$/,
                use: [ //webapck use执行顺序从右到左、链式调用,所以这里先执行css-loader
                    'style-loader',
                    'css-loader',
                ]
            }
        ]
    }

新建src/css/index.css文件 

.search-txt {
    font-size: 20px;
    color: blue;
}

在src/search.js文件中引用css文件

import React from 'react'
import ReactDOM from 'react-dom'
import './css/index.css'
class Search extends React.Component{
    render() {
        return <div className="search-txt ">search components</div>
    }
}

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

2.2.2 解析less和sass (nde-sass sass-loader 因为sass-loader 依赖node-sass)

安装依赖

npm i -D less less-loader

修改src/css/index.css文件为src/css/index.less

修改src/css/search.js内关于index.css的引用名

修改webpack文件

module:{
        rules: [
            { 
                test: /\.js$/,
                use: {
                    loader: 'babel-loader',
                }
            },
            { 
                test: /\.css$/,
                use: [ //webapck use执行顺序从右到左、链式调用,所以这里先执行css-loader
                    'style-loader',
                    'css-loader',
                ]
            },
            { 
                test: /\.less$/,
                use: [ 
                    'style-loader',
                    'css-loader',
                    'less-loader',
                ]
            }
        ]
    }
npm install --save-dev sass-loader node-sass

2.3 资源解析

2.3.1 file-loader用于处理图片

安装依赖npm i -D file-loader

在src/img文件夹下放置一张图片

在src/search.js文件中引入图片

import React from 'react'
import ReactDOM from 'react-dom'
import './css/index.less'
import fileImg from './img/fileImg.png'
class Search extends React.Component{
    render() {
        return <div className="search-txt ">search components
            <img src={fileImg}></img>
        </div>
    }
}

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

添加相关配置

module:{
        rules: [
            { 
                test: /\.js$/,
                use: {
                    loader: 'babel-loader',
                }
            },
            { 
                test: /\.css$/,
                use: [ //webapck use执行顺序从右到左、链式调用,所以这里先执行css-loader
                    'style-loader',
                    'css-loader',
                ]
            },
            { 
                test: /\.less$/,
                use: [ 
                    'style-loader',
                    'css-loader',
                    'less-loader',
                ]
            },
            use: {
                    loader: 'file-loader',
                    options: {
                        esModule: false
                    }
                }
        ]
    }

这是因为file-loader默认采用ES模块语法,即import '../image.png';然而Vue生成的是CommonJS模块语法,即require('../image.png');二者不一致。要么让file-loader或url-loader采用CommonJS语法,要么让Vue采用ES语法。刚好file-loader或url-loader有一个esModule选项能调整,将其设置为false即可:

2.3.1 file-loader用于处理字体

新建src/font文件夹,放入字体文件

css引入,修改src/css/index.css文件

@font-face{
    font-family: 'liuKai';
    src: url('../font/liuKai.ttf')
}
.search-txt {
    font-size: 20px;
    color: blue;
    font-family: 'liuKai';
}

修改webpack配置

module:{
        rules: [
            { 
                test: /\.js$/,
                use: {
                    loader: 'babel-loader',
                }
            },
            { 
                test: /\.css$/,
                use: [ //webapck use执行顺序从右到左、链式调用,所以这里先执行css-loader
                    'style-loader',
                    'css-loader',
                ]
            },
            { 
                test: /\.less$/,
                use: [ 
                    'style-loader',
                    'css-loader',
                    'less-loader',
                ]
            },
            { 
                test: /\.(png|jpeg|gif|jpg)$/,
                use: [ 
                    'file-loader']
            },
            { 
                test: /\.(woff|woff2|eot|ttf|otf)$/,
                use: [ 
                    'file-loader']
            }
        ]
    }

2.3.2 url-loader也可以处理图片和字体,可以设置较小资源自动base64

安装依赖

npm i -D url-loader

webpack.config.js中修改关于图片处理的部分

            { 
                test: /\.(png|jpeg|gif|jpg)$/,
                use: [ 
                    {
                        loader: 'url-loader',
                        options: {
                            limit: 10240 // 限制大小为10k
                        }
                    }]
            },

url-loader和file同时使用需要注意,最好不要同级使用 

{
                test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
                use:
                {
                    loader: 'url-loader',
                    options: {
                        limit: 100024,
                        name: 'img/[name].[hash:7].[ext]',
                        esModule: false,
                        fallback: {
                            loader: 'file-loader',
                            options: {
                                name: '[name].[hash:8].[ext]'
                            }
                        }
                    }
                }
            }

2.4 文件监听(watch)

文件监听是在发现源码发生变化时,自动重新构建出新的输出文件

webpack开启监听模式,有两种方式

  • 启动webpack命令时,带上--watch参数
  • 在配置webpack.config.js中设置watch:true

缺陷:每次需要手动刷新浏览器

"dev": "cross-env NODE_ENV=development webpack --watch"

或者 

watch: true, //默认为false 不开启
    watchOptions: { // 只有watch开启时,watchOptions才会有意义
        ignored: /node_modules/, // 不坚挺的文件或文件夹,支持正则匹配
        aggregateTimeout: 300, //监听到变化后300ms再去执行,默认300ms
        poll: 1000 //判断文件是否变化的轮询间隔时间(有变化先混存,到期再更新)
    }

2.5 热更新:webpack-dev-server(配合HotModuleReplacementPlugin)

wds不刷新浏览器

wds不输出文件,而是放在内存中

安装依赖

npm install webpack-dev-server -D
  "scripts": {
    "dev": "webpack-dev-server --open"//有更新自动打开浏览器
  },

由于热更新主要是用在开发环境中,修改mode: development

HotModuleReplacementPlugin是内部插件

const webpack = require('webpack')
plugins: [
        new webpack.HotModuleReplacementPlugin()
    ],
    devServer: { // 专门为webpack-dev-server指定相关配置选项
        hot: true,
        static: {
            directory: path.resolve(__dirname, "dist"),
        }
    },

热更新的原理

  • webpack compile:将JS变异成bundle
  • HRM Server:将热更新的文件输出给HMR Runtime
  • Bundle Server:提供文件在浏览器的访问
  • HMR Runtime:会注入到浏览器,更新文件的变化
  • bundle.js: 构建输出的文件

2.5 文件指纹(ContentHash,ChunkHash,Hash)

文件指纹如何生成

webpack中有三种hash可以配置

hash 所有文件哈希值相同,只要改变内容跟之前的不一致,所有哈希值都改变,没有做到缓存意义

chunkhash 当有多个chunk,形成多个bundle时,如果只有一个chunk和一个bundle内容变了,其他的bundlehash都会发生变化,因为大家都是公用的一个hash,这个时候chunkhash的作用就出来了。它根据不同的入口文件(Entry)进行依赖文件解析、构建对应的 chunk,生成对应的哈希值

contenthash 在打包的时候我们会在js中导入css文件,因为他们是同一个入口文件,如果我只改了js得代码,但是他的css抽取生成css文件时候hash也会跟着变换。这个时候contenthash的作用就出来了。

2.5.1 js文件的指纹设置(chunkhash在生产环境使用,无法和热更新HotModuleReplacementPlugin一起使用)

设置output的filename,使用[chunkhash]// filename: '[name].[chunkhash].js'

新建一份生产环境专用的webpack配置文件webpack.prod.js,

设置mode: 'production',去掉热更新相关模块。

设置文件指纹长度filename: '[name]_[chunkhash:8].js'

设置图片、字体的文件指纹

{ 
                test: /\.(png|jpeg|gif|jpg)$/,
                use: [ 
                    {
                        loader: 'file-loader',
                        options: {
                            name: '[name]_[hash:8][ext]'
                        }
                    }]
            },
            { 
                test: /\.(woff|woff2|eot|ttf|otf)$/,
                use: [ 
                    {
                        loader: 'file-loader',
                        options: {
                            name: '[name]_[hash:8][ext]'
                        }
                    }]
            }
const path = require('path')

module.exports = {
    entry: {
        'index': './src/index.js',
        'search': './src/search.js'
    },
    output: {
        path: path.join(__dirname, 'dist'),
        filename: '[name]_[chunkhash:8].js'
    },
    mode: 'production',
    module:{
        rules: [
            { 
                test: /\.js$/,
                use: {
                    loader: 'babel-loader',
                }
            },
            { 
                test: /\.css$/,
                use: [ //webapck use执行顺序从右到左、链式调用,所以这里先执行css-loader
                    'style-loader',
                    'css-loader',
                ]
            },
            { 
                test: /\.less$/,
                use: [ 
                    'style-loader',
                    'css-loader',
                    'less-loader',
                ]
            },
            { 
                test: /\.(png|jpeg|gif|jpg)$/,
                use: [ 
                    {
                        loader: 'file-loader',
                        options: {
                            name: '[name]_[hash:8].[ext]'
                        }
                    }]
            },
            { 
                test: /\.(woff|woff2|eot|ttf|otf)$/,
                use: [ 
                    {
                        loader: 'file-loader',
                        options: {
                            name: '[name]_[hash:8].[ext]'
                        }
                    }]
            }
        ]
    }
}

 添加prod执行命令

    "prod":"webpack --config webpack.prod.js"

2.5.2 css文件指纹设置(plugin:MiniCssExtractPlugin将css样式抽离到css文件)

设置MiniCssExtractPlugin的filename,使用[contenthash]

// filename: '[name].[contenthash].js'

安装依赖

npm i -D mini-css-extract-plugin

引入插件,加入plugin数组中

const MiniCssExtractPlugin = require('mini-css-extract-plugin')

module.exports = {
    
    plugins: [
        new MiniCssExtractPlugin({
            filename: '[name]_[contenthash:8].css'
        });
    ]

}

同时还需要添加到依赖中。MiniCssExtractPlugin的功能是将css样式抽离到css文件,而style-loader是将样式以<style>标签的形式引入,二者有所冲突,可以去除style-loader。

{ 
                test: /\.css$/,
                use: [ //webapck use执行顺序从右到左、链式调用,所以这里先执行css-loader
                    MiniCssExtractPlugin.loader,
                    'css-loader',
                ]
            },
            { 
                test: /\.less$/,
                use: [ 
                    MiniCssExtractPlugin.loader,
                    'css-loader',
                    'less-loader',
                ]
            },

2.5.2 图片、字体文件指纹设置

设置file-loader的filename,使用[hash]

// filename: '[name].[hash].js'

2.6 文件压缩 (prod环境)

2.6.1 js 文件的压缩(内置的terser-webpack-plugin, 生产模式生效)

parallel: true,    //使用多进程并发运行 强烈建议打开,可以填写数字

const TerserPlugin = require('terser-webpack-plugin')
optimization: { // 优化
        minimize: true,
        minimizer: [
            new TerserPlugin({
                test: /\.js(\?.*)?$/i,    //匹配参与压缩的文件
                parallel: true,    //使用多进程并发运行
                terserOptions: {
                    compress: {
                        drop_console: true,//移除所有console相关代码;
                        drop_debugger: true,//移除自动断点功能;
                        pure_funcs: ["console.log", "console.error"],//配置移除指定的指令,如console.log,alert等
                        
                    },
                    format: {
                        comments: false, // 删除注释 也可写入正则配合extractComments剥离有效注释
                    },
                },
                extractComments: false // 是否将注释剥离到单独的文件中
            })
        ]
    },

2.6.2 css 文件的压缩 css-minimizer-webpack-plugin

npm install css-minimizer-webpack-plugin --save-dev

引入依赖 

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

module.exports = {
  module: {
    rules: [
      {
        test: /.s?css$/,
        use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"],
      },
    ],
  },
  optimization: {
    minimizer: [
      // 在 webpack@5 中,你可以使用 `...` 语法来扩展现有的 minimizer(即 `terser-webpack-plugin`),将下一行取消注释
      // `...`,
      new CssMinimizerPlugin(),
    ],
  },
  plugins: [new MiniCssExtractPlugin()],
};

如果还想在开发环境下启用 CSS 优化,请将 optimization.minimize 设置为 true:

webpack.config.js

// [...]
module.exports = {
  optimization: {
    // [...]
    minimize: true,
  },
};

2.6.3 html 文件的压缩(html-webpack-plugin prod环境)

修改html-webpack-plugin,设置压缩参数(模板文件不能有注释

安装依赖

npm i -D html-webpack-plugin

引入html

const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
    plugins: [
        new HtmlWebpackPlugin({
            template: path.join(__dirname,'src/search.html'),// 需要打包的文件路径
            filename: 'search.html',
            chunks: ['search'], //
            inject: true, // 设置为true,js css会自动注入html
            minify: {

            }
        }),
        new HtmlWebpackPlugin({
            template: path.join(__dirname,'src/index.html'),// 需要打包的文件路径
            filename: 'index.html',
            chunks: ['index'], //
            inject: true, // 设置为true,js css会自动注入html
            minify: {//压缩选项
                html5: true,
                collapseWhitespace: true,
                preserveLineBreaks: false,
                minifyCss: true,
                minifyJs: true,
                removeComments: false
            }
        })
    ]
}

2.7 自动清理构建目录产物(clean-webpack-plugin)

会自动清除output目录

安装依赖, 引入插件


const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {

    plugins: [
       
        new CleanWebpackPlugin()
    ]
}

2.8 自动补齐css前缀(postcss-loader、autoprefixer prod环境)

安装依赖

npm i -D postcss-loader postcss  autoprefixer

            { 
                test: /\.less$/,
                use: [ 
                    MiniCssExtractPlugin.loader,
                    'css-loader',
                    'less-loader',
                    {
                        loader: 'postcss-loader',
                        
                    }
                ]
            },

新建postcss.config.js文件进行配置

module.exports = {
    plugins: [
        require('autoprefixer')({
            overrideBrowserslist: [
                'last 2 version', 
                '>1%', 
                'ios 7']
        })
    ]
  }

2.8 移动端CSS px自动转换成rem 

px2rem-loader设置尺寸稿。 

lib-flexible保证

安装依赖

npm i -D  px2rem-loader 
//动态计算根元素单位
npm i lib-flexible -S

lib-flexible会自动在html的head中添加一个meta name="viewport"的标签,同时会自动设置html的font-size为屏幕宽度除以10,也就是1rem等于html根节点的font-size。

配合2.91的raw-loader使用

引入使用


    module.exports = {
        
        module: {
            rules: [
                {
                    test: /\.less$/,
                    use: [
                        MiniCssExtractPlugin.loader,
                        'css-loader',
                        'less-loader',
                        {
                            loader: 'postcss-loader',
                        },
                        {
                            loader: 'px2rem-loader',
                            options: {
                                remUnit: 75, // 1rem = 75px
                                remPrecision: 8 // 转换时的位数
                            }
                        }
                    ]
                },
                
            ]
        }
    }

在html内引入lib-flexible代码,后期使用2.8.1介绍raw-loader引入

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="root"></div>
    <script>
        $( require('raw-loader!babel-loader./meta.html') )

        ; (function (win, lib) {
            var doc = win.document;
            var docEl = doc.documentElement;
            var metaEl = doc.querySelector('meta[name="viewport"]');
            var flexibleEl = doc.querySelector('meta[name="flexible"]');
            var dpr = 0;
            var scale = 0;
            var tid;
            var flexible = lib.flexible || (lib.flexible = {});

            if (metaEl) {
                console.warn('将根据已有的meta标签来设置缩放比例');
                var match = metaEl.getAttribute('content').match(/initial\-scale=([\d\.]+)/);
                if (match) {
                    scale = parseFloat(match[1]);
                    dpr = parseInt(1 / scale);
                }
            } else if (flexibleEl) {
                var content = flexibleEl.getAttribute('content');
                if (content) {
                    var initialDpr = content.match(/initial\-dpr=([\d\.]+)/);
                    var maximumDpr = content.match(/maximum\-dpr=([\d\.]+)/);
                    if (initialDpr) {
                        dpr = parseFloat(initialDpr[1]);
                        scale = parseFloat((1 / dpr).toFixed(2));
                    }
                    if (maximumDpr) {
                        dpr = parseFloat(maximumDpr[1]);
                        scale = parseFloat((1 / dpr).toFixed(2));
                    }
                }
            }

            if (!dpr && !scale) {
                var isAndroid = win.navigator.appVersion.match(/android/gi);
                var isIPhone = win.navigator.appVersion.match(/iphone/gi);
                var devicePixelRatio = win.devicePixelRatio;
                if (isIPhone) {
                    // iOS下,对于2和3的屏,用2倍的方案,其余的用1倍方案
                    if (devicePixelRatio >= 3 && (!dpr || dpr >= 3)) {
                        dpr = 3;
                    } else if (devicePixelRatio >= 2 && (!dpr || dpr >= 2)) {
                        dpr = 2;
                    } else {
                        dpr = 1;
                    }
                } else {
                    // 其他设备下,仍旧使用1倍的方案
                    dpr = 1;
                }
                scale = 1 / dpr;
            }

            docEl.setAttribute('data-dpr', dpr);
            if (!metaEl) {
                metaEl = doc.createElement('meta');
                metaEl.setAttribute('name', 'viewport');
                metaEl.setAttribute('content', 'initial-scale=' + scale + ', maximum-scale=' + scale + ', minimum-scale=' + scale + ', user-scalable=no');
                if (docEl.firstElementChild) {
                    docEl.firstElementChild.appendChild(metaEl);
                } else {
                    var wrap = doc.createElement('div');
                    wrap.appendChild(metaEl);
                    doc.write(wrap.innerHTML);
                }
            }

            function refreshRem() {
                var width = docEl.getBoundingClientRect().width;
                if (width / dpr > 540) {
                    width = 540 * dpr;
                }
                var rem = width / 10;
                docEl.style.fontSize = rem + 'px';
                flexible.rem = win.rem = rem;
            }

            win.addEventListener('resize', function () {
                clearTimeout(tid);
                tid = setTimeout(refreshRem, 300);
            }, false);
            win.addEventListener('pageshow', function (e) {
                if (e.persisted) {
                    clearTimeout(tid);
                    tid = setTimeout(refreshRem, 300);
                }
            }, false);

            if (doc.readyState === 'complete') {
                doc.body.style.fontSize = 12 * dpr + 'px';
            } else {
                doc.addEventListener('DOMContentLoaded', function (e) {
                    doc.body.style.fontSize = 12 * dpr + 'px';
                }, false);
            }


            refreshRem();

            flexible.dpr = win.dpr = dpr;
            flexible.refreshRem = refreshRem;
            flexible.rem2px = function (d) {
                var val = parseFloat(d) * this.rem;
                if (typeof d === 'string' && d.match(/rem$/)) {
                    val += 'px';
                }
                return val;
            }
            flexible.px2rem = function (d) {
                var val = parseFloat(d) / this.rem;
                if (typeof d === 'string' && d.match(/px$/)) {
                    val += 'rem';
                }
                return val;
            }

        })(window, window['lib'] || (window['lib'] = {}));

    </script>

</body>

</html>

2.8.1 静态资源内联 raw-loader

资源内联的意义

代码层面:

  • 页面框架的初始化脚本
  • 上报相关打点
  • css内联避免页面闪动

请求层面:减少HTTP网络请求数

  • 小图片或者字体内联(url-loader)

HTML和JS内联 raw-loader(读取文件返回一个string,插入合适的位置)

css内联

  • 借助style-loader
  • html-inline-css-webpack-plugin

安装依赖

npm i raw-loader@0.5.1 -D

新建src/meta.html文件,存储相关配置,作为静态资源等待引入

<meta name="keywords" content="CSDN博客,CSDN学院,CSDN论坛,CSDN直播">
<meta name="description"
    content="CSDN是全球知名中文IT技术交流平台,创建于1999年,包含原创博客、精品问答、职业培训、技术论坛、资源下载等产品服务,提供原创、优质、完整内容的专业IT技术开发社区.">
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui">
<meta name="referrer" content="always">

修改src/index.html文件(模板文件不可有注释,下列代码注释仅作解释说明,不可正式使用)

<!DOCTYPE html>
<html lang="en">
<head>
    <!-- 引入元标签 -->
    <%=require('raw-loader!./meta.html')%>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="root"></div>
    <script>
        // 引入js静态资源
        <%=require("raw-loader!babel-loader!../node_modules/lib-flexible/flexible.js")%>
    </script>
</body>
</html>

2.9 多页面应用

每一次页面跳转的时候,后台服务器都会返回一个新的html,这种类型的网站也就是多页网站,也叫做多页应用。

每个页面对应一个entry、一个html-webpack-plugin

缺点:每次新增或删除页面需要改wenpack配置

前端工程化之强大的glob语法 - 掘金

利用glob.sync

  • entry: glob.sync(path: path.join(__dirname, './src/*/index.js'))

安装依赖

npm i -D glob

文件夹变动,并改动相关引用文件路径。

  • src/index.html=>src/index/index.html 
  • src/index.js=>src/index/index.js 
  • src/helloworld.js=>src/index/helloworld.js
  • src/search.html=>src/search/index.html
  • src/search.js=>src/search/index.js

修改webpack入口文件、htmlwebpackplugins配置

const glob = require('glob')
const setMPA = ()=>{
    const entry = {}
    const htmlWebpackPlugins = []
    const entryFiles = glob.sync(path.join(__dirname, './src/*/index.js'))
    Object.keys(entryFiles).map((index)=>{
        const entryFile = entryFiles[index]
        const match = entryFile.match(/src\/(.*)\/index\.js/)
        const pageName = match && match[1]
        entry[pageName] = entryFile
        htmlWebpackPlugins.push(
            new HtmlWebpackPlugin({
                template: path.join(__dirname, `src/${pageName}/index.html`), // 需要打包的文件路径
                filename: `${pageName}.html`,
                chunks: [pageName], // 关联entry文件入口名
                inject: true, // 设置为true,js css会自动注入html
                minify: {
                    html5: true,
                    collapseWhitespace: true,
                    preserveLineBreaks: false,
                    minifyCss: true,
                    minifyJs: true,
                    removeComments: false
                }
            })
        )
    })
    return {
        entry,
        htmlWebpackPlugins
    }
}
const { entry, htmlWebpackPlugins} = setMPA()
module.exports = {
    entry: entry,
    output: {
        path: path.join(__dirname, 'dist'),
        filename: '[name]_[chunkhash:8].js'
    },
    mode: 'production',

    plugins: [
        
    ].concat(htmlWebpackPlugins)
}

2.10 使用source map

作用:运行代码与源代码之间完全不同,不方便调试应用、定位错误信息,source map就是用来映射转换之后的代码与源代码之间的关系。

开发环境开启,线上环境关闭(线上排查问题可以将sourcemap上传到错误监控系统)

webpack支持多source-map实现方式,效率和效果也不相同。

  • eval:仅能定位文件 不生成source-map
  • eval-source-map:可以定位到文件、行列信息  生成source-map
  • cheap-eval-source-map:只能定位到行    生成source-map
  • cheap-module-eval-source-map:定位代码与源代码一模一样
  •  特征:eval是否使用eval执行代码
  • cheap-source-map:是否包含行信息
  • module是否能够得到loader处理之前的源代码
  • inline-source-map以data-url形式嵌入代码,会增大代码体积
  • hidden-source-map开发第三方包使用,并没有通过注释的方式引入,所以浏览器看不到效果。
  • nosources-source-map没有源代码,但同样提供了行列信息

webpack选择合适的source-map选择

  • 开发模式:eval-cheap-module-source-map
  • 发布打包:不使用sourcemap,避免暴露源代码或者使用nosources-source-map
    devtool: 'source-map'

2.11 提取公共页面资源

思路:比如将react、react-dom基础包通关cdn引入,不参与打包

方法:

  • 使用html-webpack-externals-plugin
  • 利用splitchunksplugin进行公共脚本分离

2.11.1 通过splitchunksplugin提取公共页面资源

chunks参数说明

  • async同步引入的库进行分离(默认)
  • initial同步引入的库进行分离
  • all 所有引入的库进行分离(推荐)

test:匹配出需要分离的包

  • minChunks:设置最小引用次数
  • minuSize:分离包的体积大小
    optimization: { // 优化
       
        splitChunks: {
            chunks: 'async',
            minSize: 20000, // 分离包的体积大小
            minChunks: 1, // 设置最小引用次数
            minRemainingSize: 0, 
            // minRemainingSize 通过确保拆分后剩余的最小 chunk 体积超过限制来避免大小为零的模块。 
            // 'development' 模式 中默认为 0。对于其他情况,splitChunks.minRemainingSize 默认为 splitChunks.minSize 的值,
            // 因此除需要深度控制的极少数情况外,不需要手动指定它。
            // maxAsyncRequests: 30, // 按需加载时的最大并行请求数。
            // maxInitialRequests: 30, // 入口点的最大并行请求数。
            enforceSizeThreshold: 50000, // 强制执行拆分的体积阈值和其他限制(minRemainingSize,maxAsyncRequests,maxInitialRequests)将被忽略。
            cacheGroups: { 
                //  缓存组可以继承和/或覆盖来自 splitChunks.* 的任何选项。
                //  但是 test、priority 和 reuseExistingChunk 只能在缓存组级别上进行配置。将它们设置为 false以禁用任何默认缓存组。
                //  缓存组可以继承和/或覆盖来自
              defaultVendors: {
                test: /[\\/]node_modules[\\/]/,
                priority: -10, // 一个模块可以属于多个缓存组。优化将优先考虑具有更高 priority(优先级)的缓存组。默认组的优先级为负,以允许自定义组获得更高的优先级(自定义组的默认值为 0
                reuseExistingChunk: true,
              },
              default: {
                minChunks: 2,
                priority: -20,
                reuseExistingChunk: true, // 如果当前 chunk 包含已从主 bundle 中拆分出的模块,则它将被重用,而不是生成新的模块。这可能会影响 chunk 的结果文件名。
              },
            },
        },
    },

打包公共资源

新建common/index.js文件

2.11.2 通过html-webpack-externals-plugin提取公共页面资源

安装依赖

npm i -D  html-webpack-externals-plugin
const htmlWebpackExternalsPlugin = require('html-webpack-externals-plugin')

module.exports = {

        new htmlWebpackExternalsPlugin({
            externals: [
                {
                    module: 'react',
                    entry: 'https://11.url.cn/now/lib/16.2.0/react.min.js',
                    global: 'React'
                },
                {
                    module: 'react-dom',
                    entry: 'https://11.url.cn/now/lib/16.2.0/react-dom.min.js',
                    global: 'ReactDOM'
                }
            ]
        })
    ]

}

2.12 tree shaking 摇树优化(webpack内置)

使用 webpack默认支持,在.babelre里设置modules: false即可

production mode的情况下默认开启,测试时设置为none

要求:必须是es6的语法,CJS不支持

DCE( dead code elimination)

代码不会被执行,不可到达

代码执行的结果不会被用到

代码只会影响死变量(只写不读)

Tree-shaking原理

利用es6模块的特点:

  • 只能作为模块顶层语句出现
  • import的模块名只能死字符串常量
  • import binding是immutable的

代码擦除:uglify阶段删除无用代码

 2.13 scope hositing(webpack内置)

原理:将所有模块的代码按照引用顺序放在一个函数作用域里,然后适当的重命名一些变量以防止变量名冲突

对比:通过scope hoisting可以减少函数声明代码和内存开销

production mode的情况下默认开启

在development模式下,需要手动打开该模块

new webpack.optimize.ModuleConcatenationPlugin()

要求:必须是es6的语法,CJS不支持

 2.14 代码分割和动态加载

webpack可以将代码库分割成chunks(愉快),当代码运行到需要它们的时候再进行加载。

适用的场景:

  • 抽离相同代码到一个共享块
  • 脚本懒加载,使得初始下载的代码更小

懒加载JS脚本的方式

  • CommonJS: require.ensure
  • ES6: 动态import (需要babel转换)

安装依赖

 npm i -D  babel-plugin-syntax-dynamic-import

修改.babelra配置,使其支持动态import

{
    "presets": [
        "@babel/preset-env",
        "@babel/preset-react"
    ],
    "plugins": [
        "@babel/plugin-syntax-dynamic-import"
    ]
}

修改src/search/search.js文件

import React from 'react'
import ReactDOM from 'react-dom'
import '../css/index.less'
import fileImg from '../img/fileImg.png'
import  '../../common/index'
class Search extends React.Component{
    constructor() {
        super(...arguments)
        this.state = {
            Text: null
        }
    }
    loadComponent() {
        import('./text.js').then((Text)=>{
            console.log('Text', Text)
            this.setState({
                Text: Text.default
            })
        })
    }
    render() {
        const { Text } = this.state
        return <div className="search-txt ">search component
                {Text? <Text></Text>: null}
                测试字体
            <img src={fileImg} onClick={()=>this.loadComponent()}></img>
        </div>
    } 
}

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

 2.15 webpack和ESLint结合

方案一:webpack与CI/CD集成

方案二:webpack与ESLint集成

2.15.1 webpack与CI/CD集成

本地开发阶段增加precommit钩子

2.15.2 webpack与ESLint集成 (打包)

安装依赖

npm i -D eslint-config-airbnb eslint@^7.2.0 eslint-plugin-import@^2.22.1 eslint-plugin-jsx-a11y@^6.4.1 eslint-plugin-react@^7.21.5 
npm i -D eslint-loader
npm i -D babel-eslint 

修改webpack配置文件

module.exports = {

    module: {
        rules: [
            {
                test: /\.js$/,
                use: [
                    'babel-loader',
                    'eslint-loader'
                ]
            },
            {            
            
        ]
    },

}

新建.eslintrc.js文件,设置eslint规则

module.exports = {
    "parser": "babel-eslint", //制定解析器
    "extends": "airbnb"
    "env": {  //JavaScript 可在文件中使用注释来指定环境
        "browser": true,
        "node": true
    }
    "rules": {
        "semi": "error" //结尾应当有分号
    }
}

打包,然后demo收获一堆报错……

2.16 webpack打包库和组件   terser-webpack-plugin

webpack除了可以用来打包应用,也可以用来打包js库

实现一个大整数加法库的打包,要求:

  • 需要打包压缩版和非压缩版本
  • 支持AMD/CJS/ESM模块引入

建立一个新的项目文件夹large-number

初始化项目,并安装依赖

npm init -y

npm i webpack webpack-cli -D

新建src/index.js文件,存放大整数加法

export default function add(a, b){
    let i = a.length - 1
    let j = b.length - 1
    let carry = 0 // 进位
    let res = ''
    while ( i >= 0 || j>=0 ){
        let x = 0; // a某一位的值
        let y = 0; // b某一位的值
        let sum 
        if( i >= 0 ){
            x = a[i] - '0'
            i --
        }
        if( j >= 0 ){
            y = b[j] - '0'
            j --
        }
        sum = x + y + carry
        if( sum >= 10 ){
            carry = 1
            sum -= 10
        }else {
            carry = 0
        }
        res = sum + res
    }
    if( carry ){
        res = carry + res
    }
    return res
}

修改打包文件 

module.exports = {
    entry: {
        'large-number': './src/index.js',
        'large-number.min': './src/index.js'
    },
    output: {
        filename: '[name].js',
        library: 'largeNUmber', // 打包后库的的名字
        libraryTarget: 'umd',  // 打包类库的发布格式,这里使用UMD
        libraryExport: 'default' // 对外暴露default属性,就可以直接调用default里的属性
    }
}

 添加打包命令

    "build": "webpack"

如果就此打包的话,'large-number'、'large-number.min'均会被压缩,需要进一步改动

npm i -D terser-webpack-plugin

修改webpack配置,设置使用环境、使用 terser-webpack-plugin插件

const terserWebpackPlugin = require('terser-webpack-plugin')
module.exports = {
    mode: 'none', // 避免全部自动压缩,打包出未压缩文件
    entry: {
        'large-number': './src/index.js',
        'large-number.min': './src/index.js'
    },
    output: {
        filename: '[name].js',
        library: 'largeNUmber', // 打包后库的的名字
        libraryTarget: 'umd',  // 打包类库的发布格式,这里使用UMD
        libraryExport: 'default' // 对外暴露default属性,就可以直接调用default里的属性
    },
    optimization: {
        minimize: true,
        minimizer: [new terserWebpackPlugin({
            include: /\.min\.js$/
        })],
    },
}

新建index.js文件

if(process.env.NODE_ENV === 'production'){
    module.exports = require('./dist/large-number.min.js')
}else {
    module.exports = require('./dist/large-number.js')
}

发布npm包

npm login
npm publish

 在01project项目中使用打包

npm i -D gu-large-number

  直接使用

import '../../common/index'
import largeNumber from 'gu-large-number'
export function helloworld(){
    return  'hello awebpack'+largeNumber(10,20)
}

2.17 功能模块设计和目录结构:

2.17.1 基础配置

  • 资源解析
  • 样式增强
  • 目录清理
  • 多页面打包
  •  命令行打包和优化
  • css提取成一个单独的文件

2.17.2 开发阶段配置

  • 代码热更新
  • sourcemap

通过webpack-merge

安装依赖

npm i -D webpack-merge
const merge = require('webpack-merge')
const baseConfig = require('./webpack.base.js')
const webpack = require('webpack')

const devConfig = {
    mode: 'development',
    plugins:[
        new webpack.HotModuleReplacementPlugin(),
    ],
    devServer: { // 专门为webpack-dev-server指定相关配置选项
        contentBase: './dist',
        hot: true,
        stats: 'error-only'
    },
    devtool: 'cheap-module-eval-source-map'
}

module.exports = merge(baseConfig, devCOnfig)

2.17.2 生产阶段配置

  • 代码压缩
  • 文件修改
  • tree-shaking
  • scope hositing
  • 速度优化
  • 提及优化

安装依赖

npm i -D eslint babel-eslint eslint-config-airbnb-base

新建.eslintrc.js配置文件,配置eslint规则

module.exports = {
    "parser": "babel-eslint", //制定解析器
    "extends": "airbnb-base"
    "env": {  //JavaScript 可在文件中使用注释来指定环境
        "browser": true,
        "node": true
    }
    "rules": {
        // "semi": "error" //结尾应当有分号
    }
}

增加运行命令

"eslint": "eslint --fix"

三、分析webpack

3.1、使用webpack内置的stats

在package.json中设置新命令

"build:stats": "webpack --config webpack.prod.js --json > stats.json",

启动,得到一个stats.json文件,着实不直观……

3.2、速度分析:使用speed-measure-webpack-plugin

可以看到每个loader和插件执行耗时

安装依赖

npm i -D speed-measure-webpack-plugin

webpack引入

const speedMeasureWebpackPlugin = require('speed-measure-webpack-plugin')

const smp = new speedMeasureWebpackPlugin()

module.exports = smp.wrap({
})

报错: 

{
                test: /\.less$/,
                use: [
                    MiniCssExtractPlugin.loader,
                    'css-loader',
                    'less-loader',
                    {
                        loader: 'postcss-loader',
                    },
                    {
                        loader: 'px2rem-loader',
                        options: {
                            remUnit: 75, // 1rem = 75px
                            remPrecision: 8 // 转换时的位数
                        }
                    }
                ]
            },
ERROR in ./src/css/index.less
Module build failed (from ./node_modules/mini-css-extract-plugin/dist/loader.js):
Error: You forgot to add 'mini-css-extract-plugin' plugin (i.e. `{ plugins: [new MiniCssExtractPlugin()] }`), please read https://github.com/webpack-contrib/mini-css-extract-plugin#getting-started
    at Object.pitch (/Users/gujianxiang/Demo/study/webpack/01project/node_modules/mini-css-extract-plugin/dist/loader.js:43:14)
 @ ./src/search/index.js 25:0-27

暂未解决……

3.3 体积分析:使用webpack-bundle-analyzer

可以分析哪些问题?

  • 依赖的第三方模块文件大小
  • 业务组件大小

安装依赖

npm i -D webpack-bundle-analyzer

修改配置文件

const webpackBundleAnalyzer = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
module.exports = {
    plugins: [
        new webpackBundleAnalyzer(),
    ],
}

 script命令,重点在于--progress

    "analyzer": "cross-env ENV_TYPE=staging ANALYZER=true webpack --progress"

3.3 Babel-polyfill 的作用 

用了babel还需要polyfill吗??? - 掘金

Babel默认只转换新的JavaScript句法(syntax),而不转换新的API,比如 Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise等全局对象,以及一些定义在全局对象上的方法(比如Object.assign)都不会转码。举个栗子,ES6在Array对象上新增了Array.from方法。Babel就不会转码这个方法。如果想让这个方法运行,必须使用babel-polyfill,为当前环境提供一个垫片。

npm i -D @babel/runtime
npm install -D @babel/cli @babel/core
npm i -D @babel/plugin-transform-runtime

3.4 多进程多实例   thread-loader terser-webpack-plugin多进程并发运行 parallel: true

使用时,需将此 loader 放置在其他 loader 之前。放置在此 loader 之后的 loader 会在一个独立的 worker 池中运行。

use: [
  {
    loader: "thread-loader",
    // 有同样配置的 loader 会共享一个 worker 池
    options: {
      // 产生的 worker 的数量,默认是 (cpu 核心数 - 1),或者,
      // 在 require('os').cpus() 是 undefined 时回退至 1
      workers: 2,

      // 一个 worker 进程中并行执行工作的数量
      // 默认为 20
      workerParallelJobs: 50,

      // 额外的 node.js 参数
      workerNodeArgs: ['--max-old-space-size=1024'],

      // 允许重新生成一个僵死的 work 池
      // 这个过程会降低整体编译速度
      // 并且开发环境应该设置为 false
      poolRespawn: false,

      // 闲置时定时删除 worker 进程
      // 默认为 500(ms)
      // 可以设置为无穷大,这样在监视模式(--watch)下可以保持 worker 持续存在
      poolTimeout: 2000,

      // 池分配给 worker 的工作数量
      // 默认为 200
      // 降低这个数值会降低总体的效率,但是会提升工作分布更均一
      poolParallelJobs: 50,

      // 池的名称
      // 可以修改名称来创建其余选项都一样的池
      name: "my-pool"
    },
  },
  // 耗时的 loader(例如 babel-loader)
];

使用terser-webpack-plugin多进程并发运行 parallel: true,    //使用多进程并发运行 强烈建议打开,可以填写数字 

3.5 缩小构建目标

3.5.1、比如babel-loader不解析node_modules

exclude: 'node_modules'
include: ''

3.6 图片压缩 image-webpack-loader

module.exports = {
...
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        use: [
          {
            loader: 'file-loader',
            options: {
              name: '[name].[hash:7].[ext]'
            },
          },
          {
            loader: 'image-webpack-loader',
            options: {
              mozjpeg: {
                progressive: true,
                quality: 50,
              },
              optipng: {
                enabled: true,
              },
              pngquant: {
                quality: [0.5, 0.65],
                speed: 4,
              },
              gifsicle: {
                interlaced: false,
              },
              webp: { // 不支持WEBP就不要写这一项
                quality: 75
              },
            },
          },
        ],
      },
    ],
  },
}

3.7 擦除无效的css purgecss-webpack-plugin

npm i -D purgecss-webpack-plugin

通过 mini-css-extract-plugin 插件使用

const path = require('path')
const glob = require('glob')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const PurgecssPlugin = require('purgecss-webpack-plugin')

const PATHS = {
  src: path.join(__dirname, 'src')
}

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.join(__dirname, 'dist')
  },
  optimization: {
    splitChunks: {
      cacheGroups: {
        styles: {
          name: 'styles',
          test: /\.css$/,
          chunks: 'all',
          enforce: true
        }
      }
    }
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          MiniCssExtractPlugin.loader,
          "css-loader"
        ]
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: "[name].css",
    }),
    new PurgecssPlugin({
      paths: glob.sync(`${PATHS.src}/**/*`,  { nodir: true }),
    }),
  ]
}

3.7 预编译资源模块

[打包优化]Webpack DLL - 掘金

认识DLL库

  • 什么是DLL

    • DLL全称是动态链接库(Dynamic Link Library),是为软件在Windows中实现共享函数库的一种实现方式;
    • 那么webpack中也有内置DLL的功能,它指的是可以将可以共享,并且不经常改变的代码,抽取成一个共享的库;
    • 这个库在之后编译的过程中,会被引入到其他项目的代码中,减少的打包的时间;
  • DDL库的使用分为两步:

    • 第一步:打包一个DLL库;
    • 第二步:项目中引入DLL库

3.7.1 预编译资源模块 

新建一个处理dll的webpack处理文件,新建一个指向该文件的script命令

const path = require("path");
const { DllPlugin } = require("webpack");
// const { merge } = require("webpack-merge")

// const commonConfig = (isProduction) => {
module.exports = {
    // return {
    context: path.join(__dirname, "./"),
    entry: {
        library: ["react", "react-dom"]
    },
    mode: process.env.NODE_ENV,
    output: {
        path: path.join(__dirname, "dll"),
        filename: "dll_[name].js",
        library: "dll_[name]"
    },
    plugins: [
        new DllPlugin({
            name: '[name].manifest.json',
            path: path.resolve(__dirname, "./dll/[name].manifest.json")
        })
    ],
    // }
}
// const devConfig = require('./webpack.dev')
// const prodConfig = require('./webpack.prod')

// module.exports = function (env) {
//     const isProduction = env.production;
//     process.env.NODE_ENV = isProduction ? "production" : "development"

//     const config = isProduction ? prodConfig : devConfig
//     return merge(commonConfig(isProduction), config)
// }

打包完成后,在根目录下会有一个dll文件夹,内有Dll文件和相应manifest.json文件。

DLL使用

  • 如果我们的代码中使用了react、react-dom,有配置splitChunks的情况下,他们会进行分包,打包到 一个独立的chunk中。
    • 但是现在有了dll_react,不再需要单独去打包它们,可以直接去引用dll_react即可:
    • 第一步:通过DllReferencePlugin插件告知要使用的DLL库;
    • 第二步:通过AddAssetHtmlPlugin插件,将打包的DLL库引入到Html模块中;
new webpack.DllReferencePlugin({
            // context:path.join(__dirname, ""),
            manifest:path.join(__dirname,"dll/library.manifest.json")
        }),

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值