1. 什么是WebPack #

WebPack可以看做是模块打包机:它做的事情是,分析你的项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Scss,TypeScript等),并将其打包为合适的格式以供浏览器使用。

webpack

构建就是把源代码转换成发布到线上的可执行 JavaScrip、CSS、HTML 代码,包括如下内容。

构建其实是工程化、自动化思想在前端开发中的体现,把一系列流程用代码去实现,让代码自动化地执行这一系列复杂的流程。 构建给前端开发注入了更大的活力,解放了我们的生产力。

2. 初始化项目 #

mkdir zhufeng-webpack
cd zhufeng-webpack
npm init -y

3. 快速上手 #

3.1 webpack核心概念 #

Webpack 启动后会从Entry里配置的Module开始递归解析 Entry 依赖的所有 Module。 每找到一个 Module, 就会根据配置的Loader去找出对应的转换规则,对 Module 进行转换后,再解析出当前 Module 依赖的 Module。 这些模块会以 Entry 为单位进行分组,一个 Entry 和其所有依赖的 Module 被分到一个组也就是一个 Chunk。最后 Webpack 会把所有 Chunk 转换成文件输出。 在整个流程中 Webpack 会在恰当的时机执行 Plugin 里定义的逻辑。

3.2 配置webpack #

npm install webpack webpack-cli -D

3.2.1 创建src目录 #

mkdir src

3.2.2 创建dist目录 #

mkdir dist

3.2.3 基本配置文件 #

webpack.config.js

const path=require('path');
module.exports={
  context:process.cwd(),
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname,'dist'),
        filename:'bundle.js'
    },
    module: {},
    plugins: [],
    devServer: {}
}
const path=require('path');
module.exports={
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname,'dist'),
        filename:'bundle.js'
    },
    module: {},
    plugins: [],
    devServer: {}
}

3.2.4 创建index.html文件 #

在dist目录下创建index.html文件

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

3.2.5 mode #

common

//parent chunk中解决了的chunk会被删除
optimization.removeAvailableModules:true
//删除空的chunks
optimization.removeEmptyChunks:true
//合并重复的chunk
optimization.mergeDuplicateChunks:true

development

//调试
devtool:eval
//缓存模块, 避免在未更改时重建它们。
cache:true
//缓存已解决的依赖项, 避免重新解析它们。
module.unsafeCache:true
//在 bundle 中引入「所包含模块信息」的相关注释
output.pathinfo:true
//在可能的情况下确定每个模块的导出,被用于其他优化或代码生成。
optimization.providedExports:true
//找到chunk中共享的模块,取出来生成单独的chunk
optimization.splitChunks:true
//为 webpack 运行时代码创建单独的chunk
optimization.runtimeChunk:true
//编译错误时不写入到输出
optimization.noEmitOnErrors:true
//给模块有意义的名称代替ids
optimization.namedModules:true
//给模chunk有意义的名称代替ids
optimization.namedChunks:true

production

//性能相关配置
performance:{hints:"error"....}
//某些chunk的子chunk已一种方式被确定和标记,这些子chunks在加载更大的块时不必加载
optimization.flagIncludedChunks:true
//给经常使用的ids更短的值
optimization.occurrenceOrder:true
//确定每个模块下被使用的导出
optimization.usedExports:true
//识别package.json or rules sideEffects 标志
optimization.sideEffects:true
//尝试查找模块图中可以安全连接到单个模块中的段。- -
optimization.concatenateModules:true
//使用uglify-js压缩代码
optimization.minimize:true

4. 配置开发服务器 #

npm i webpack-dev-server –D
+ devServer:{
+        contentBase:path.resolve(__dirname,'dist'),
+        host:'localhost',
+        compress:true,
+        port:8080
+ }
+  "scripts": {
+    "build": "webpack",
+    "dev": "webpack-dev-server --open "
+  }

5. 支持加载css文件 #

5.1 什么是Loader #

通过使用不同的Loader,Webpack可以要把不同的文件都转成JS文件,比如CSS、ES6/7、JSX等

5.2 loader三种写法 #

5.2.1 loader #

加载CSS文件,CSS文件有可能在node_modules里,比如bootstrap和antd

module: {
        rules: [
            {
                test: /\.css/,
+                loader:['style-loader','css-loader']
            }
        ]
    }

5.2.2 use #

module: {
        rules: [
            {
                test: /\.css/,
+                use:['style-loader','css-loader']
            }
        ]
    },

5.2.3 use+loader #

    module: {
        rules: [
            {
                test: /\.css/,
                include: path.resolve(__dirname,'src'),
                exclude: /node_modules/,
                use: [{
                    loader: 'style-loader',
                    options: {
                        insert:'top'
                    }
                },'css-loader']
            }
        ]
    }

6. 插件 #

6.1 自动产出html #

+    +entry:{
+        index:'./src/index.js',  // chunk名字 index
+        common:'./src/common.js' //chunk名字 common
+    },

    plugins: [
+       new HtmlWebpackPlugin({
+            template:'./src/index.html',//指定模板文件
+            filename:'index.html',//产出后的文件名
+            inject:false,
+            hash:true,//为了避免缓存,可以在产出的资源后面添加hash值
+            chunks:['common','index'],
+            chunksSortMode:'manual'//对引入代码块进行排序的模式
+        }),
    )]
<head>
+ <% for (var css in htmlWebpackPlugin.files.css) { %>
+        <link href="<%= htmlWebpackPlugin.files.css[css] %>" rel="stylesheet">
+ <% } %>
</head>
<body>
+ <% for (var chunk in htmlWebpackPlugin.files.chunks) { %>
+ <script src="<%= htmlWebpackPlugin.files.chunks[chunk].entry %>"></script>
+ <% } %>
</body>

7. 支持图片 #

7.1 手动添加图片 #

npm i file-loader url-loader -D

7.2 JS中引入图片 #

7.2.1 JS #

let logo=require('./images/logo.png');
let img=new Image();
img.src=logo;
document.body.appendChild(img);

7.2.2 webpack.config.js #

{
  test:/\.(jpg|png|bmp|gif|svg)/,
    use:[
    {
       loader:'url-loader',
       options:{limit:4096}
    }
  ]
}

7.3 在CSS中引入图片 #

还可以在CSS文件中引入图片

7.3.1 CSS #

.logo{
    width:355px;
    height:133px;
    background-image: url(./images/logo.png);
    background-size: cover;
}

7.3.2 HTML #

<div class="logo"></div>

8. 分离CSS #

因为CSS的下载和JS可以并行,当一个HTML文件很大的时候,我们可以把CSS单独提取出来加载

8.1 安装依赖模块 #

npm install --save-dev mini-css-extract-plugin

8.2 配置webpack.config.js #

plugins: [
       //参数类似于webpackOptions.output
+        new MiniCssExtractPlugin({
+            filename: '[name].css',
+            chunkFilename:'[id].css'
+        }),

{
                test: /\.css/,
                include: path.resolve(__dirname,'src'),
                exclude: /node_modules/,
                use: [{
+                    loader: MiniCssExtractPlugin.loader
                },'css-loader']
            }

8.3 内联CSS #

cnpm i html-inline-css-webpack-plugin -D
+const HtmlInlineCssWebpackPlugin= require('html-inline-css-webpack-plugin').default;

plugins:[
  new HtmlWebpackPlugin({}),
+  new HtmlInlineCssWebpackPlugin()
]

8.4 压缩JS和CSS #

cnpm i uglifyjs-webpack-plugin terser-webpack-plugin optimize-css-assets-webpack-plugin -D

const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
const TerserPlugin = require('terser-webpack-plugin');
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
module.exports = {
    mode: 'production',
    optimization: {
        minimizer: [
           /*  new UglifyJsPlugin({
                cache: true,//启动缓存
                parallel: true,//启动并行压缩
                //如果为true的话,可以获得sourcemap
                sourceMap: true // set to true if you want JS source maps
            }), */
            new TerserPlugin({
                 parallel: true,
                 cache: true
            }),
            //压缩css资源的
            new OptimizeCSSAssetsPlugin({
                 assetNameRegExp:/\.css$/g,
                 //cssnano是PostCSS的CSS优化和分解插件。cssnano采用格式很好的CSS,并通过许多优化,以确保最终的生产环境尽可能小。
                 cssProcessor:require('cssnano')
            })
        ]
    },

8.5 css和image存放单独目录 #

{
   loader:MiniCssExtractPlugin.loader,
      options:{
+        publicPath:'/'
      }        
{
  test:/\.(jpg|jpeg|png|bmp|gif|svg|ttf|woff|woff2|eot)/,
  use:[
        {
          loader:'url-loader',
          options:{
              limit: 4096,
+              outputPath: 'images',
+              publicPath:'/images'
          }
        }
     ]
}
output: {
        path: path.resolve(__dirname,'dist'),
        filename: 'bundle.js',
+        publicPath:'/'
    },
{
  test:/\.(jpg|jpeg|png|bmp|gif|svg|ttf|woff|woff2|eot)/,
  use:[
        {
          loader:'url-loader',
          options:{
              limit: 4096,
+              outputPath: 'images',
+              publicPath:'/images'
          }
        }
     ]
}

plugins: [
    new MiniCssExtractPlugin({
-       //filename: '[name].css',
-       //chunkFilename: '[id].css',
+       chunkFilename: 'css/[id].css',
+       filename: 'css/[name].[hash].[chunkhash].[contenthash].css',//name是代码码chunk的名字
    }),


8.6 文件指纹 #

8.6.1 文件指纹如何生成 #

指纹占位符

占位符名称 含义
ext 资源后缀名
name 文件名称
path 文件的相对路径
folder 文件所在的文件夹
contenthash 文件的内容hash,默认是md5生成
hash 文件内容的hash,默认是md5生成
emoji 一个随机的指代文件内容的emoj

9. 编译less 和 sass #

9.1 安装less #

npm i less less-loader -D
npm i node-sass sass-loader -D

9.2 编写样式 #

less

@color:red;
.less-container{
    color:@color;
}

scss

$color:green;
.sass-container{
    color:$color;
}

webpack.config.js

   {
        test: /\.less/,
        include: path.resolve(__dirname,'src'),
        exclude: /node_modules/,
        use: [{
            loader: MiniCssExtractPlugin.loader,
        },'css-loader','less-loader']
    },
    {
        test: /\.scss/,
        include: path.resolve(__dirname,'src'),
        exclude: /node_modules/,
        use: [{
            loader: MiniCssExtractPlugin.loader,
        },'css-loader','sass-loader']
    },

10. 处理CSS3属性前缀 #

npm i postcss-loader autoprefixer -D

postcss-loader

index.css

::placeholder {
    color: red;
}

postcss.config.js

module.exports={
    plugins:[require('autoprefixer')]
}

webpack.config.js

{
   test:/\.css$/,
   use:[MiniCssExtractPlugin.loader,'css-loader','postcss-loader'],
   include:path.join(__dirname,'./src'),
   exclude:/node_modules/
}

11. 转义ES6/ES7/JSX #

11.1 安装依赖包 #

npm i babel-loader @babel/core @babel/preset-env  @babel/preset-react  -D
npm i @babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties -D

11.2 decorator #

//Option+Shift+A
function readonly(target,key,discriptor) {
    discriptor.writable=false;
}

class Person{
    @readonly PI=3.14;
}
let p1=new Person();
p1.PI=3.15;
console.log(p1)

jsconfig.json

{
    "compilerOptions": {
        "experimentalDecorators": true
    }
}

11.3 webpack.config.js #

{
    test: /\.jsx?$/,
    use: {
        loader: 'babel-loader',
        options:{
         "presets": ["@babel/preset-env"],
         "plugins": [
            ["@babel/plugin-proposal-decorators", { "legacy": true }],
            ["@babel/plugin-proposal-class-properties", { "loose" : true }]
         ]
        }
    },
    include: path.join(__dirname,'src'),
    exclude:/node_modules/
}

.babelrc

{
  "presets": ["@babel/preset-env"],
  "plugins": [
    ["@babel/plugin-proposal-decorators", { "legacy": true }],
    ["@babel/plugin-proposal-class-properties", { "loose" : true }]
  ]
}

11.4 babel runtime #

npm install --save-dev @babel/plugin-transform-runtime
npm install --save @babel/runtime

.babelrc

{
  "presets": ["@babel/preset-env"],
  "plugins": [
    ["@babel/plugin-proposal-decorators", { "legacy": true }],
    ["@babel/plugin-proposal-class-properties", { "loose" : true }]
    [
         "@babel/plugin-transform-runtime",
         {
            "corejs": false,
            "helpers": true,
            "regenerator": true,
            "useESModules": true
        }
    ]
  ]
}

webpack打包的时候,会自动优化重复引入公共方法的问题

11.4.1 区别 #

11.5 ESLint校验代码格式规范 #

11.5.1 标准配置 #

npm install eslint eslint-loader babel-eslint --D

.eslintrc.js

module.exports = {
    root: true,
    //指定解析器选项
    parserOptions: {
        sourceType: 'module',
        ecmaVersion: 2015
    },
    //指定脚本的运行环境
    env: {
        browser: true,
    },
    // 启用的规则及其各自的错误级别
    rules: {
        "indent": ["error", 4],//缩进风格
        "quotes": ["error", "double"],//引号类型 
        "semi": ["error", "always"],//关闭语句强制分号结尾
        "no-console": "error",//禁止使用console
        "arrow-parens": 0 //箭头函数用小括号括起来
    }
}

webpack.config.js

module: {
        //配置加载规则
        rules: [
            {
                test: /\.js$/,
                loader: 'eslint-loader',
                enforce: "pre",
                include: [path.resolve(__dirname, 'src')], // 指定检查的目录
                options: { fix: true } // 这里的配置项参数将会被传递到 eslint 的 CLIEngine   
            },

11.5.2 继承airbnb #

cnpm i eslint-config-airbnb eslint-loader eslint eslint-plugin-import eslint-plugin-react eslint-plugin-react-hooks and eslint-plugin-jsx-a11y -D

.eslintrc.js

module.exports = {
    "parser":"babel-eslint",
    "extends":"airbnb",
    "rules":{
        "semi":"error",
        "no-console":"off",
        "linebreak-style":"off",
        "eol-last":"off"
        //"indent":["error",2]
    },
    "env":{
        "browser":true,
        "node":true
    }
}

11.6 引入字体 #

11.6.1 配置loader #

{
 test:/\.(woff|ttf|eot|svg|otf)$/,
     use:{
                    //url内部内置了file-loader
        loader:'url-loader',
        options:{//如果要加载的图片大小小于10K的话,就把这张图片转成base64编码内嵌到html网页中去
       limit:10*1024
       }
   }
 },

11.6.2 使用字体 #

@font-face {
    src: url('./fonts/HabanoST.otf') format('truetype');
    font-family: 'HabanoST';
}

.welcome {
    font-size:100px;
    font-family: 'HabanoST';
}

12. 如何调试打包后的代码 #

类型 含义
source-map 原始代码 最好的sourcemap质量有完整的结果,但是会很慢
eval-source-map 原始代码 同样道理,但是最高的质量和最低的性能
cheap-module-eval-source-map 原始代码(只有行内) 同样道理,但是更高的质量和更低的性能
cheap-eval-source-map 转换代码(行内) 每个模块被eval执行,并且sourcemap作为eval的一个dataurl
eval 生成代码 每个模块都被eval执行,并且存在@sourceURL,带eval的构建模式能cache SourceMap
cheap-source-map 转换代码(行内) 生成的sourcemap没有列映射,从loaders生成的sourcemap没有被使用
cheap-module-source-map 原始代码(只有行内) 与上面一样除了每行特点的从loader中进行映射

看似配置项很多, 其实只是五个关键字eval、source-map、cheap、module和inline的任意组合

关键字 含义
eval 使用eval包裹模块代码
source-map 产生.map文件
cheap 不包含列信息(关于列信息的解释下面会有详细介绍)也不包含loader的sourcemap
module 包含loader的sourcemap(比如jsx to js ,babel的sourcemap),否则无法定义源文件
inline 将.map作为DataURI嵌入,不单独生成.map文件

12.1 sourcemap #

12.1 生成sourcemap #

script.js

let a=1;
let b=2;
let c=3;
java -jar compiler.jar --js script.js --create_source_map ./script-min.js.map --source_map_format=V3 --js_output_file script-min.js

script-min.js

var a=1,b=2,c=3;

script-min.js.map

{
"version":3,
"file":"script-min.js",
"lineCount":1,
"mappings":"AAAA,IAAIA,EAAE,CAAN,CACIC,EAAE,CADN,CAEIC,EAAE;",
"sources":["script.js"],
"names":["a","b","c"]
}
字段 含义
version:Source Source map的版本,目前为3
file:转换后的文件名。 转换后的文件名
sourceRoot 转换前的文件所在的目录。如果与转换前的文件在同一目录,该项为空。
sources 转换前的文件。该项是一个数组,表示可能存在多个文件合并。
names 转换前的所有变量名和属性名
mappings 记录位置信息的字符串

12.2 mappings属性 #

对应 含义
第一层是行对应 以分号(;)表示,每个分号对应转换后源码的一行。所以,第一个分号前的内容,就对应源码的第一行,以此类推。
第二层是位置对应 以逗号(,)表示,每个逗号对应转换后源码的一个位置。所以,第一个逗号前的内容,就对应该行源码的第一个位置,以此类推。
第三层是位置转换 以VLQ编码表示,代表该位置对应的转换前的源码位置。
"mappings":"AAAA,IAAIA,EAAE,CAAN,CACIC,EAAE,CADN,CAEIC,EAAE;",

12.3 位置对应的原理 #

位置 含义
第一位 表示这个位置在(转换后的代码的)的第几列
第二位 表示这个位置属于sources属性中的哪一个文件
第三位 表示这个位置属于转换前代码的第几行
第四位 表示这个位置属于转换前代码的第几
第五位 表示这个位置属于names属性中的哪一个变量

首先,所有的值都是以0作为基数的。其次,第五位不是必需的,如果该位置没有对应names属性中的变量,可以省略第五位,再次,每一位都采用VLQ编码表示;由于VLQ编码是变长的,所以每一位可以由多个字符构成

如果某个位置是AAAAA,由于A在VLQ编码中表示0,因此这个位置的五个位实际上都是0。它的意思是,该位置在转换后代码的第0列,对应sources属性中第0个文件,属于转换前代码的第0行第0列,对应names属性中的第0个变量。

12.4 VLQ编码 #

base64

base64vlq在线转换

以16来做示例吧

  1. 将16改写成二进制形式10000
  2. 在最右边补充符号位。因为16大于0,所以符号位为0,整个数变成100000
  3. 从右边的最低位开始,将整个数每隔5位,进行分段,即变成1和00000两段。如果最高位所在的段不足5位,则前面补0,因此两段变成00001和00000
  4. 将两段的顺序倒过来,即00000和00001
  5. 在每一段的最前面添加一个"连续位",除了最后一段为0,其他都为1,即变成100000和000001
  6. 将每一段转成Base 64编码。
  7. 查表可知,100000为g,000001为B。因此,数值16的VLQ编码为gB
let base64 = [
    'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
    'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
    'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
    'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
];

function encode(num) {
    debugger;
    let binary = (num).toString(2);// 10000 转成二进制 
    binary = num > 0 ? binary + '0' : binary + '1';//正数最后边补0,负数最右边补1   100000
    //00001 00000
    let zero = 5 - (binary.length % 5);//4
    if (zero > 0) {
        binary = binary.padStart(Math.ceil(binary.length / 5) * 5, '0');
    }// 00001 00000
    let parts = [];
    for (let i = 0; i < binary.length; i += 5) {
        parts.push(binary.slice(i, i + 5));
    }
    parts.reverse();// ['00000','00001']
    for (let i = 0; i < parts.length; i++) {
        if (i === parts.length - 1) {
            parts[i] = '0' + parts[i];// ['100000','000001']
        } else {
            parts[i] = '1' + parts[i];
        }
    }
    let chars = [];
    for (let i = 0; i < parts.length; i++) {
        chars.push(base64[parseInt(parts[i], 2)]);
    }
    return chars.join('')
}
//16需要二个字符
let ret = encode(16);
console.log(ret);

function getValue(char) {
    let index = base64.findIndex(item => item == char);
    let str = (index).toString(2);
    str = str.padStart(6, '0');
    str = str.slice(1, -1);
    return parseInt(str, 2);
}

function decode(chars) {
    let values = [];
    for (let i = 0; i < chars.length; i++) {
        values.push(getValue(chars[i]));
    }
    return values;
}
function desc(values) {
    return `
    第${values[1] + 1}个源文件中
    的第1行
    第${values[0] + 1}列,
    对应转换后的第${values[2] + 1}行
    第${values[3] + 1}列,
    对应第${values[4] + 1}个变量`;
}
let ret2 = decode('IAAIA');
let message = desc(ret2);
console.log(ret2, message);

13.打包第三方类库 #

13.1 直接引入 #

import _ from 'lodash';
alert(_.join(['a','b','c'],'@'));

13.2 插件引入 #

+ new webpack.ProvidePlugin({
+     _:'lodash'
+ })

没有全局的$函数,所以导入依赖全局变量的插件依旧会失败

13.3 expose-loader #

13.4 externals #

如果我们想引用一个库,但是又不想让webpack打包,并且又不影响我们在程序中以CMD、AMD或者window/global全局等方式进行使用,那就可以通过配置externals

 const jQuery = require("jquery");
 import jQuery from 'jquery';
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
+ externals: {
+         jquery: 'jQuery'//如果要在浏览器中运行,那么不用添加什么前缀,默认设置就是global
+ },
module: {

13.5 html-webpack-externals-plugin #

+ const htmlWebpackExternalsPlugin= require('html-webpack-externals-plugin');
new htmlWebpackExternalsPlugin({
            externals:[
                {
                    module:'react',
                    entry:'https://cdn.bootcss.com/react/15.6.1/react.js',
                    global:'React'
                },
                 {
                    module:'react-dom',
                    entry:'https://cdn.bootcss.com/react/15.6.1/react-dom.js',
                    global:'ReactDOM'
                }
            ]
}) 

14. watch #

当代码发生修改后可以自动重新编译

module.exports = {
    //默认false,也就是不开启
    watch:true,
    //只有开启监听模式时,watchOptions才有意义
    watchOptions:{
        //默认为空,不监听的文件或者文件夹,支持正则匹配
        ignored:/node_modules/,
        //监听到变化发生后会等300ms再去执行,默认300ms
        aggregateTimeout:300,
        //判断文件是否发生变化是通过不停的询问文件系统指定议是有变化实现的,默认每秒问1000次
        poll:1000
    }
}

15. 添加商标 #

+ new webpack.BannerPlugin('珠峰培训'),

16. 拷贝静态文件 #

有时项目中没有引用的文件也需要打包到目标目录

npm i copy-webpack-plugin -D
new CopyWebpackPlugin([{
  from: path.resolve(__dirname,'src/assets'),//静态资源目录源地址
  to:path.resolve(__dirname,'dist/assets') //目标地址,相对于output的path目录
}])

17. 打包前先清空输出目录 #

npm i  clean-webpack-plugin -D
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
plugins:[
new CleanWebpackPlugin({cleanOnceBeforeBuildPatterns: ['**/*', '!static-files*'],})
]

18. 服务器代理 #

如果你有单独的后端开发服务器 API,并且希望在同域名下发送 API 请求 ,那么代理某些 URL 会很有用。

18.1 不修改路径 #

proxy: {
  "/api": 'http://localhost:3000'
}

18.2 修改路径 #

proxy: {
    "/api": {
       target: 'http://localhost:3000',
       pathRewrite:{"^/api":""}        
    }            
}

18.3 before after #

before 在 webpack-dev-server 静态资源中间件处理之前,可以用于拦截部分请求返回特定内容,或者实现简单的数据 mock。

before(app){
  app.get('/api/users', function(req, res) { 
    res.json([{id:1,name:'zfpx1'}])
  })
}

18.4 webpack-dev-middleware #

webpack-dev-middleware就是在 Express 中提供 webpack-dev-server 静态服务能力的一个中间件

npm install webpack-dev-middleware --save-dev
const express = require('express');
const app = express();
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const webpackOptions = require('./webpack.config');
webpackOptions.mode = 'development';
const compiler = webpack(webpackOptions);
app.use(webpackDevMiddleware(compiler, {}));
app.listen(3000);

19. resolve解析 #

19.1 extensions #

指定extension之后可以不用在require或是import的时候加文件扩展名,会依次尝试添加扩展名进行匹配

resolve: {
  extensions: [".js",".jsx",".json",".css"]
},

19.2 alias #

配置别名可以加快webpack查找模块的速度

const bootstrap = path.resolve(__dirname,'node_modules/_bootstrap@3.3.7@bootstrap/dist/css/bootstrap.css');
resolve: {
+    alias:{
+        "bootstrap":bootstrap
+    }
},

19.3 modules #

19.4 mainFields #

默认情况下package.json 文件则按照文件中 main 字段的文件名来查找文件

resolve: {
  // 配置 target === "web" 或者 target === "webworker" 时 mainFields 默认值是:
  mainFields: ['browser', 'module', 'main'],
  // target 的值为其他时,mainFields 默认值为:
  mainFields: ["module", "main"],
}

19.5 mainFiles #

当目录下没有 package.json 文件时,我们说会默认使用目录下的 index.js 这个文件,其实这个也是可以配置的

resolve: {
  mainFiles: ['index'], // 你可以添加其他默认使用的文件名
},

19.6 resolveLoader #

resolve.resolveLoader用于配置解析 loader 时的 resolve 配置,默认的配置:

module.exports = {
  resolveLoader: {
    modules: [ 'node_modules' ],
    extensions: [ '.js', '.json' ],
    mainFields: [ 'loader', 'main' ]
  }
};

20. noParse #

21. DefinePlugin #

DefinePlugin创建一些在编译时可以配置的全局常量

new webpack.DefinePlugin({
    PRODUCTION: JSON.stringify(true),
    VERSION: "1",
    EXPRESSION: "1+2",
    COPYRIGHT: {
        AUTHOR: JSON.stringify("珠峰培训")
    }
})
console.log(PRODUCTION);
console.log(VERSION);
console.log(EXPRESSION);
console.log(COPYRIGHT);

22. IgnorePlugin #

IgnorePlugin用于忽略某些特定的模块,让 webpack 不把这些指定的模块打包进去

import moment from  'moment';
console.log(moment);
new webpack.IgnorePlugin(/^\.\/locale/,/moment$/)

20. 区分环境变量 #

20.1 环境差异 #

20.2 获取mode参数 #

npm install --save-dev optimize-css-assets-webpack-plugin
  "scripts": {
+    "dev": "webpack-dev-server --env=development --open"
  },
const TerserWebpackPlugin = require('terser-webpack-plugin');
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
module.exports=(env,argv) => ({
    optimization: {
        minimizer: argv.mode == 'production'?[            
            new TerserWebpackPlugin({
               parallel:true,//开启多进程并行压缩
               cache:true//开启缓存
      }),
            new OptimizeCssAssetsWebpackPlugin({})
        ]:[]
    }
})

20.3 封装log方法 #

export default function log(...args) {
    if (process.env.NODE_ENV == 'development') {
        console.log.apply(console,args);
    }
}

20.4 拆分配置 #

可以把 webpack 的配置按照不同的环境拆分成多个文件,运行时直接根据环境变量加载对应的配置即可

const { smart } = require('webpack-merge')
const webpack = require('webpack')
const base = require('./webpack.base.js')
module.exports = smart(base, {
  module: {
    rules: [],
  }
})

21. 对图片进行压缩和优化 #

image-webpack-loader可以帮助我们对图片进行压缩和优化

npm install image-webpack-loader --save-dev
 {
          test: /\.(png|svg|jpg|gif|jpeg|ico)$/,
          use: [
            'file-loader',
+           {
+             loader: 'image-webpack-loader',
+             options: {
+               mozjpeg: {
+                 progressive: true,
+                 quality: 65
+               },
+               optipng: {
+                 enabled: false,
+               },
+               pngquant: {
+                 quality: '65-90',
+                 speed: 4
+               },
+               gifsicle: {
+                 interlaced: false,
+               },
+               webp: {
+                 quality: 75
+               }
+             }
+           },
          ]
        }

22. 多入口MPA #

const path=require('path');
const HtmlWebpackPlugin=require('html-webpack-plugin');
const htmlWebpackPlugins=[];
const glob = require('glob');
const entry={};
const entryFiles = glob.sync('./src/**/index.js');
entryFiles.forEach((entryFile,index)=>{
    let entryName = path.dirname(entryFile).split('/').pop();
    entry[entryName]=entryFile;
    htmlWebpackPlugins.push(new HtmlWebpackPlugin({
        template:`./src/${entryName}/index.html`,
        filename:`${entryName}/index.html`,
        chunks:[entryName],
        inject:true,
        minify:{
            html5:true,
            collapseWhitespace:true,
            preserveLineBreaks:false,
            minifyCSS:true,
            minifyJS:true,
            removeComments:false
        }
    }));
}); 

module.exports={
    entry,
    plugins: [
        //other plugins
        ...htmlWebpackPlugins
    ]
}

23. 日志优化 #

预设 替代 描述
errors-only none 只在错误时输出
minimal none 发生错误和新的编译时输出
none false 没有输出
normal true 标准输出
verbose none 全部输出

22.1 friendly-errors-webpack-plugin #

cnpm i friendly-errors-webpack-plugin
+ stats:'verbose',
  plugins:[
+   new FriendlyErrorsWebpackPlugin()
  ]

编译完成后可以通过echo $?获取错误码,0为成功,非0为失败

24. 日志输出 #

  "scripts": {
    "build": "webpack",
+    "build:stats":"webpack --json > stats.json",
    "dev": "webpack-dev-server --open"
  },
const webpack = require('webpack');
const config = require('./webpack.config.js');
webpack(config,(err,stats)=>{
  if(err){
    console.log(err);
  }
  if(stats.hasErrors()){
    return console.error(stats.toString("errors-only"));
  }
  console.log(stats);
});

25.费时分析 #

const SpeedMeasureWebpackPlugin = require('speed-measure-webpack-plugin');
const smw = new SpeedMeasureWebpackPlugin();
module.exports =smw.wrap({
});

26.webpack-bundle-analyzer #

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
module.exports={
  plugins: [
    new BundleAnalyzerPlugin()  // 使用默认配置
    // 默认配置的具体配置项
    // new BundleAnalyzerPlugin({
    //   analyzerMode: 'server',
    //   analyzerHost: '127.0.0.1',
    //   analyzerPort: '8888',
    //   reportFilename: 'report.html',
    //   defaultSizes: 'parsed',
    //   openAnalyzer: true,
    //   generateStatsFile: false,
    //   statsFilename: 'stats.json',
    //   statsOptions: null,
    //   excludeAssets: null,
    //   logLevel: info
    // })
  ]
}
{
 "scripts": {
    "dev": "webpack --config webpack.dev.js --progress"
  }
}

webpack.config.js

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
module.exports={
  plugins: [
    new BundleAnalyzerPlugin({
      analyzerMode: 'disabled', // 不启动展示打包报告的http服务器
      generateStatsFile: true, // 是否生成stats.json文件
    }),
  ]
}
{
 "scripts": {
    "generateAnalyzFile": "webpack --profile --json > stats.json", // 生成分析文件
    "analyz": "webpack-bundle-analyzer --port 8888 ./dist/stats.json" // 启动展示打包报告的http服务器
  }
}
npm run generateAnalyzFile
npm run analyz

27.polyfill #

27.1 babel-polyfill #

27.2 polyfill-service #

<script src="https://polyfill.io/v3/polyfill.min.js"></script>

28. libraryTarget 和 library #

libraryTarget 使用者的引入方式 使用者提供给被使用者的模块的方式
var 只能以script标签的形式引入我们的库 只能以全局变量的形式提供这些被依赖的模块
commonjs 只能按照commonjs的规范引入我们的库 被依赖模块需要按照commonjs规范引入
amd 只能按amd规范引入 被依赖的模块需要按照amd规范引入
umd 可以用script、commonjs、amd引入 按对应的方式引入

28.1 var (默认) #

编写的库将通过var被赋值给通过library指定名称的变量。

28.1.1 index.js #

module.exports =  {
    add(a,b) {
        return a+b;
    }
}

28.1.2 bundle.js #

var calculator=(function (modules) {}({})

29.1.3 index.html #

    <script src="bundle.js"></script>
    <script>
        let ret = calculator.add(1,2);
        console.log(ret);
    </script>

28.2 commonjs #

编写的库将通过 CommonJS 规范导出。

28.2.1 导出方式 #

exports["calculator"] = (function (modules) {}({})

28.2.2 使用方式 #

require('npm-name')['calculator'].add(1,2);

npm-name是指模块发布到 Npm 代码仓库时的名称

28.3 commonjs2 #

编写的库将通过 CommonJS 规范导出。

28.3.1 导出方式 #

module.exports = (function (modules) {}({})

28.3.2 使用方式 #

require('npm-name').add();

在 output.libraryTarget 为 commonjs2 时,配置 output.library 将没有意义。

28.4 this #

编写的库将通过 this 被赋值给通过 library 指定的名称,输出和使用的代码如下:

28.4.1 导出方式 #

this["calculator"]= (function (modules) {}({})

28.4.2 使用方式 #

this.calculator.add();

28.5 window #

编写的库将通过 window 被赋值给通过 library 指定的名称,即把库挂载到 window 上,输出和使用的代码如下:

28.5.1 导出方式 #

window["calculator"]= (function (modules) {}({})

28.5.2 使用方式 #

window.calculator.add();

28.6 global #

编写的库将通过 global 被赋值给通过 library 指定的名称,即把库挂载到 global 上,输出和使用的代码如下:

28.6.1 导出方式 #

global["calculator"]= (function (modules) {}({})

28.6.2 使用方式 #

global.calculator.add();

28.6 umd #

(function webpackUniversalModuleDefinition(root, factory) {
  if(typeof exports === 'object' && typeof module === 'object')
    module.exports = factory();
  else if(typeof define === 'function' && define.amd)
    define([], factory);
  else if(typeof exports === 'object')
    exports['MyLibrary'] = factory();
  else
    root['MyLibrary'] = factory();
})(typeof self !== 'undefined' ? self : this, function() {
  return _entry_return_;
});

29. 打包库和组件 #

29.1 编写库文件 #

29.1.1 webpack.config.js #

const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
    mode:'none',
    entry:{
        'zhufengmath':'./src/index.js',
        'zhufengmath.min':'./src/index.js'
    },
    optimization:{
        minimize:true,
        minimizer:[
            //可以支持es6,默认的使用TerserPlugin
            new TerserPlugin({
                include:/\.min\.js/
            })
        ]
    },
    output:{
        filename:'[name].js',
        library:'zhufengmath',//配置导出库的名称
        libraryExport:'default',
        libraryTarget:'umd'//配置以何种方式导出库,是字符串的枚举类型
    }
};

29.1.2 package.json #

 "scripts": {
+    "build": "webpack",

29.1.3 index.js #

//zhufengnodejs zhufengjiagou
if(process.env.NODE_ENV == 'production'){
    module.exports = require('./dist/zhufengmath.min.js');
}else{
    module.exports = require('./dist/zhufengmath.js');
}

29.1.4 src\index.js #

export function add(a,b){
  return a+b;
}
export function minus(a,b){
  return a-b;
}
export function multiply(a,b){
  return a*b;
}
export function divide(a,b){
  return a/b;
}
export default {
  add,minus,multiply,divide
}

29.2 git规范和changelog #

29.2.1 良好的git commit好处 #

29.2.2 良好的commit #

cnpm i commitizen  validate-commit-msg conventional-changelog-cli -S
commitizen init cz-conventional-changelog --save --save-exact
git cz

29.2.3 提交的格式 #

<type>(<scope>):<subject/>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>
类型 含义
feat 新增feature
fix 修复bug
docs 仅仅修改了文档,比如README、CHANGELOG、CONTRIBUTE等
style 仅仅修改了空格、格式缩进、偏好等信息,不改变代码逻辑
refactor 代码重构,没有新增功能或修复bug
perf 优化相关,提升了性能和体验
test 测试用例,包括单元测试和集成测试
chore 改变构建流程,或者添加了依赖库和工具
revert 回滚到上一个版本
ci CI 配置,脚本文件等更新

29.2.4 precommit钩子 #

cnpm i husky validate-commit-msg conventional-changelog-cli --save-dev
"scripts": {
    "commitmsg": "validate-commit-msg",
    "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0"
}
29.2.4.1 conventional-changelog-cli #
$ conventional-changelog -p angular -i CHANGELOG.md -s

如果你想生成之前所有 commit 信息产生的 changelog 则需要使用这条命令

conventional-changelog -p angular -i CHANGELOG.md -s -r 0

29.3 semver版本规范 #

29.3.1 版本号说明 #

29.3.2 先行版本 #

29.3.3 升级版本 #

npm version
npm version patch 升级补丁版本号
npm version minor 升级小版本号
npm version major 升级大版本号

29.3.4 发布 #

npm adduser
npm login
npm publish

29.4 使用 #

UMD=ES Module + CJS + AMD CDN

<script src="zhufengmath.js"></script>
<script>
console.log(window.zhufengmath);
</script>

30. px 自动转成rem #

30.1 安装 #

cnpm i px2rem-loader lib-flexible -D

30.2 index.html #

index.html

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>主页</title>
    <script>
      let docEle = document.documentElement;
      function setRemUnit () {
        //750/10=75   375/10=37.5
        docEle.style.fontSize = docEle.clientWidth / 10 + 'px';
      }
      setRemUnit();
      window.addEventListener('resize', setRemUnit);
    </script>
</head>
<body>
    <div id="root"></div>
</body>

30.3 reset.css #

*{
    padding: 0;
    margin: 0;
}
#root{
    width:375px;
    height:375px;
    border:1px solid red;
    box-sizing: border-box;
}

30.4 webpack.config.js #

 {
        test:/\.css$/,//如果要require或import的文件是css的文件的话
        //从右向左处理CSS文件,loader是一个函数
        use:[{
                loader:MiniCssExtractPlugin.loader,
                options:{
                     publicPath: (resourcePath, context) => {
                        return '/';
                    }
                    //publicPath: '/'
                }
        },{
                    loader:'css-loader',
                    options:{
                        //Enables/Disables or setups number of loaders applied before CSS loader.
                        importLoaders:0
                    }
                },{
                    loader:'postcss-loader',
                    options:{
                        plugins:[
                            require('autoprefixer')
                        ]
                    }
                },{
+                    loader:'px2rem-loader',
+                    options:{
+                        remUnit:75,
+                        remPrecesion:8
+                    }
+                }]
+            },

31. 内联资源 #

31.1 webpack.config.js #

const path = require('path');
const HtmlWebpackPlugin  = require("html-webpack-plugin");
module.exports = {
    entry: './src/index.js',
    output: {
        filename: '[name].bundle.js',
        path: path.resolve(__dirname, 'dist')
    },
    plugins:[
        new HtmlWebpackPlugin({
            title: "实现 Html、JavaScript、CSS 内联",
            template:"./src/index.html",//指定首页模板
        }),
    ]
};

31.2 安装raw-loader #

cnpm install raw-loader --save-dev

31.3 内联 #

<!--  内联html  -->
${require("raw-loader!./inline.html").default}
<!--  内联js  -->
<script>
${require("raw-loader!./inline.js").default}
</script>
<!--  内联css  -->
<style>
${require("!!raw-loader!./inline.css").default}
</style>

32.参考 #

32.1 参考文档 #

32.2 常用loader列表 #

32.3 文件 #

32.4 JSON #

32.4 转换编译(Transpiling) #

32.5 模板(Templating) #

32.6 样式 #

32.7 清理和测试(Linting && Testing) #

32.8 框架(Frameworks) #