webpack学习笔记

Posted 顾青菜

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了webpack学习笔记相关的知识,希望对你有一定的参考价值。

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

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

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

一、初识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、核心概念之Loaders 

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

二、常用Loaders

2.1、解析es6、React JSX

2.1.1 使用babel-loader

安装相关依赖

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: {
                        presets: ["@babel/preset-env"],
                        plugins: ['@babel/plugin-proposal-object-rest-spread']
                    }
                }
            }
        ]
    }

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

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

2.1.2 解析React JSX

安装相关依赖

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.2 css解析相关

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

安装依赖

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',
                ]
            }
        ]
    }

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',
                ]
            },
            { 
                test: /\\.(png|jpeg|gif|jpg)$/,
                use: [ 
                    'file-loader']
            }
        ]
    }

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
                        }
                    }]
            },

2.4 文件监听(watch)

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

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

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

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

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: 和整个项目构建相关,只要项目文件有修改,整个项目构建的hash值就会更改

ChunkHash: 和webpack打包的chunk有关,不同的entry会生成不同的chunkhash的值

ContentHash: 根据文件内容来定义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)

设置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 文件的压缩(内置的uglifyjs-webpack-plugin)

const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')

module.exports = {
    
    plugins: [
        
        new OptimizeCssAssetsWebpackPlugin({
            assetNameRegExp: /\\.css$/g,
            cssProcessor: require('cssnano')
        })
    ]
}

2.6.2 css 文件的压缩 optimize-css-assets-webpack-plugin

使用optimize-css-assets-webpack-plugin

同时使用cssnano

安装依赖

npm i optimize-css-assets-webpack-plugin -D
npm i -D cssnano

引入依赖 

const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')

module.exports = {
    plugins: [
        new OptimizeCssAssetsWebpackPlugin()
    ]
}

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.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], 
                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支持12中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选择

  • 开发模式:cheap-module-eval-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:分离包的体积大小
module.exports = {

    optimization: {
        splitChunks: {
            minSize: 0,
            cacheGroups: {
                commons: {
                    name: "commons",
                    chunks: "all",
                    minChunks: 2
                },
                vendors: {
                    test: /(react|react-dom)/,
                    name: 'vendors',
                    chunks: 'all'
                }
            }
        }
    },
}

打包公共资源

新建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(),
    ],
}

3.3 Babel-polyfill 的作用

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

以上是关于webpack学习笔记的主要内容,如果未能解决你的问题,请参考以下文章

webpack 学习笔记

webpack学习笔记--区分环境

webpack学习笔记八

webpack学习笔记

webpackの学习笔记2

webpack学习笔记流行的前端模块化工具webpack初探