webpack搭建项目笔记

381 阅读11分钟
webpack 4.41.5版本<br/>
node: 12.13.1 版本

1.入门到进阶

1.1 初始化项目

npm init

1.2 webpack是运行在node环境中的,我们需要安装以下两个npm包

yarn add -D webpack webpack-cli

新建一个文件夹src ,然后新建文件index.js和test.js,写一点代码打印测试

index.js

console.log('lc-测试+2');
let str = require('./test.js');
console.log('str', str);

test.js

module.exports = 'test文件+2'

执行

npx webpack 

注意:执行npx webpack 命令时,会自动去 node_modules-->webpack-->webpack-cli执行依赖, 此时会看到默认生成了dist文件夹,dist-->main.js文件
这里可以研究下 dist-->main.js,其实就是模块化打包js,commandJs,递归遍历等思路;

1.3 安装vscode插件--code runner,并右键 执行 code run 命令,可以看到控制台打印了数据;

1.4 在dist文件夹里新建 index.html文件,并引用 main.js,双击打开 index.html(此时是静态绝对路径),可以看到控制台打印了数据;

1.5 package.json 添加命令

"scripts": {
        "test": "echo \"Error: no test specified\" && exit 1",
        "build": "webpack --config ./build/webpack.dev.config.js"
    },

注意:--config 后面的配置文件名可以随便定义了,默认是 webpack.config.js(或webpackfile.js)

1.6 在项目根目录新建一个build文件夹,里面新建一个webpack.dev.config.js,执行 npm run build,得到同样的结果;

1.7 在index.html引用 bundle.js

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
<div id="app"></div>
<script type="text/javascript" src="./bundle.js" charset="utf-8"></script>
</body>
</html>

src-->index.js

document.getElementById("app").innerText="learn webpack"

build/webpack.dev.config.js

let path = require('path');
console.log('路径', path.resolve(__dirname, 'dist'));
module.exports = {
    mode: 'development', //模式
    entry: './src/index.js', //入口
    output: {
        filename: 'bundle.js', //打包后的文件名
        path: path.resolve(__dirname, 'dist'), //解析出来是 绝对路径
    }
}

2.配置 webpack.base.config.js,webpack.dev.config.js,webpack.prod.config.js 三个文件

2.1 entry,output和webpack-dev-server

执行

yarn add -D webpack-dev-server,修改package.json
webpack.dev.config.js
module.exports = {
    entry: {
        main: path.resolve(__dirname, '../src/index.js'), // 入口文件
    },
    output: {
        filename: '[name].[hash:8].js', // 打包后的文件名称
        path: path.resolve(__dirname, '../dist') // 打包后的目录
    },
    devServer: {
        port: 3000, //端口号
        host: '0.0.0.0', // 允许ip访问
        hot: true, //热更新
        compress: true, // gzip压缩
        progress: true, //进度条
        contentBase: './dist', //必须配置的选项,服务启动的目录,默认为跟目录
        // 当使用 HTML5 History API 时,任意的 404 响应都可能需要被替代为 index.html。通过设置为 true 进行启用
        historyApiFallback: {
            disableDotRule: true
        },
        // 出现错误时是否在浏览器上出现遮罩层提示
        overlay: true,
        // 设置接口请求代理,更多 proxy 配置请参考 https://github.com/chimurai/http-proxy-middleware#options
        proxy: {
            '/api/': {
                changeOrigin: true, //解决跨域
                // 目标地址
                target: 'http://localhost:3000',
                // 重写路径
                pathRewrite: {
                    '^/api/': '/'
                }
            }
        }

    },
}

2.2 配置html模板和friendly-errors-webpack-plugin(展示更有好的提示功能)

js文件打包好了,但是我们不可能每次在html文件中手动引入打包好的js

之前我们一直通过webpack里面的

contentBase: path.join(__dirname, '../dist'),

配置获取dist/index.html来访问。需要手动写死引入的打包JS,比较麻烦。这个插件,每次会自动把js插入到你的模板index.html里面去。

yarn add html-webpack-plugin friendly-errors-webpack-plugin -D

然后注释webpack的contentBase配置,并在根目录下新建public目录,将public下的index.html移动到dist下,然后删除手动引入的js

webpack.dev.config.js
const path = require('path');
const HTMLWebpackPlugin = require('html-webpack-plugin');
const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin');
module.exports = {
    //...省略
    entry: {
        main: path.resolve(__dirname, '../src/index.js'), // 入口文件
        //header: path.resolve(__dirname, '../src/header.js') //多入口
    },
    output: {
        filename: '[name].[hash:8].js', // 打包后的文件名称
        path: path.resolve(__dirname, '../dist') // 打包后的目录
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: path.resolve(__dirname, '../public/index.html'),
            filename: "index.html",
            chunks: ['main'] // 与入口文件对应的模块名
        }),
        //new HtmlWebpackPlugin({
            template: path.resolve(__dirname, '../public/header.html'),
            filename: 'header.html',
            chunks: ['header'] // 与入口文件对应的模块名
        }),
        new FriendlyErrorsWebpackPlugin(),
    ]
}

2.3 clean-webpack-plugin 我们发现每次打包,只要改动后都会增加文件,怎么自动清空之前的打包内容呢?webpack提供了clean-webpack-plugin插件

yarn add clean-webpack-plugin -D
webpack.config.prod.js
const {CleanWebpackPlugin} = require('clean-webpack-plugin')
module.exports = {
    // ...省略其他配置
    plugins:[...,new CleanWebpackPlugin()] // 每次打包前清空
}

2.4 babel

Babel 把用最新标准编写的 JavaScript代码向下编译成可以在今天随处可用的版本。 (本教程使用的babel版本是7,请注意包名和配置与6的不同)

@babel/core 调用Babel的API进行转码 @babel/preset-env 用于解析 ES6 @babel/preset-react 用于解析 JSX babel-loader 加载器

yarn add @babel/core @babel/preset-env @babel/preset-react babel-loader -D

然后在根目录下新建一个babel配置文件

babel.config.js

const babelConfig = {
   presets: ["@babel/preset-react", "@babel/preset-env"],
    plugins: [...]
}

module.exports = babelConfig;

修改webpack.dev.config.js,增加babel-loader!

/*src目录下面的以.js|.jsx结尾的文件,要使用babel解析*/
/*cacheDirectory是用来缓存编译结果,下次编译加速*/
module: {
    rules: [{
        test: /\.js|jsx$/,
        use: ['babel-loader?cacheDirectory=true'],
        include: path.join(__dirname, '../src'),
        exclude: /node_modules/,
    }]
}

默认值为 false。之后的 webpack构建,将会尝试读取缓存,来避免在每次执行时,可能产生的、高性能消耗的 Babel 重新编译过程。

2.5 @babel/polyfill

上面的babel-loader只会将 ES6/7/8语法转换为ES5语法,但是对新api并不会转换 例如(promise、Generator、Set、Maps、Proxy等) 此时我们需要借助babel-polyfill来帮助我们转换

yarn add -D @babel/polyfill
// webpack.dev.config.js
const path = require('path')
module.exports = {
    //省略
    entry: ["@babel/polyfill",path.resolve(__dirname,'../src/index.js')],    // 入口文件 活着可以在 src/index.js  直接引用
}

2.6 引用css

js 文件相关的 babel-loader 配置好了,但是有时候我们想在项目中为元素添加一些样式,而 webpack 中认为一切都是模块,所以我们这时候也需要别的 loader 来解析一波样式代码了

  • style-loader:将 css 插入到页面的 style 标签。
  • css-loader:处理 css 文件中的 url() 等。
  • postcss-loader:可以集成很多插件,用来操作 css。我们这里使用它集成 autoprefixer 来自动添加前缀。
  • less-loader:是将 less 文件编译成 css。

编译的机制是从右到左,从下到上

npm install style-loader css-loader less less-loader  --save-dev

1).开启 webpack 提供的 CSS Module,
2).由于等会儿会使用 antd,所以引入 antd 时需要开启 less 的 javascript 选项,所以要将 less-loader 中的属性 javascriptEnabled 设置为 true。

2.7 mini-css-extract-plugin 插件提取 CSS 代码

注:配置 mini-css-extract-plugin 插件:

  • 在 plugins 属性中引入
  • 将 module 的 rules 中使用的 style-loader 替换为 MiniCssExtractPlugin.loader
webpack.dev.config.js
...
const autoprefixer = require('autoprefixer');
const isProd = process.env.NODE_ENV === 'production';

module.exports = {
    ...,
    plugins: [...],
    module: {
        rules: [
            ...,
                {
                test: /\.(css|less)$/,
                include: path.join(__dirname, '../src'),
                use: [
                    isProd ? MiniCssExtractPlugin.loader : 'style-loader',
                    {
                        loader: 'css-loader',
                        options: {
                            sourceMap: true,
                            modules: true,
                            localIdentName: '[local]--[hash:base64:5]'
                        }
                    },
                    'postcss-loader',
                    {
                        loader: 'less-loader',
                        options: {
                            javascriptEnabled: true
                        }
                    }
                ]
            },
            {
                test: /\.(css|less)$/,
                include: path.join(__dirname, '../node_modules'),
                use: [
                    'style-loader',
                    'css-loader',
                    {
                        loader: 'less-loader',
                        options: {
                            javascriptEnabled: true
                        }
                    }
                ]
            },
                ]
            },
            {
                test: /\.(css|less)$/,
                include: /node_modules/,
                use: [{
                        loader: 'style-loader'
                    },
                    {
                        loader: 'css-loader',
                        options: {}
                    },
                    {
                        loader: 'postcss-loader'
                    },
                    {
                        loader: 'less-loader',
                        options: {
                            javascriptEnabled: true
                        }
                    }
                ]
            },
        ]
    }
}

2.8 集成PostCSS

为css添加前缀

yarn add postcss-loader postcss-cssnext -D
postcss-cssnext 允许你使用未来的 CSS 特性(包括 autoprefixer)。

然后在根目录下新建postcss.config.js

module.exports = {
    plugins: {
        'postcss-cssnext': {}
    }
};

2.9 拆分压缩css(optimize-css-assets-webpack-plugin)

我们发现使用了生产环境的mode配置以后,JS是压缩了,但是css并没有压缩

注意:虽然webpack 5可能内置了CSS minimizer,但是你需要携带自己的webpack 4。设置optimization.minimizer会覆盖webpack提供的默认值,因此请务必同时指定JS minimalizer:
yarn add optimize-css-assets-webpack-plugin -D
webpack.prod.config.js

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

plugins: [
    ...
    new OptimizeCssAssetsPlugin()
],

2.10 图片、字体、媒体等

  • file-loader就是将文件在进行一些处理后(主要是处理文件名和路径、解析文件url),并将文件移动到输出的目录中
  • url-loader 一般与file-loader搭配使用
 {
        test: /\.(jpe?g|png|gif)$/i, //图片文件
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 10240,
              fallback: {
                loader: 'file-loader',
                options: {
                    name: 'img/[name].[hash:8].[ext]'
                }
              }
            }
          }
        ]
      },
      {
        test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, //媒体文件
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 10240,
              fallback: {
                loader: 'file-loader',
                options: {
                  name: 'media/[name].[hash:8].[ext]'
                }
              }
            }
          }
        ]
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i, // 字体
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 10240,
              fallback: {
                loader: 'file-loader',
                options: {
                  name: 'fonts/[name].[hash:8].[ext]'
                }
              }
            }
          }
        ]
      },
    ]
  }
}

2.11 添加 resolve allias 属性,设置别名

webpack.dev.config.js

resolve: {
+        alias: {
+            src: path.resolve(__dirname, '../src'),
+        }
+    }

2.12 添加 resolve.modules 属性,指明第三方模块存放位置

一般进行模块搜索时,会从当前目录下的 node_modules 一直搜索到磁盘根目录下的 node_modules。所以为了减少搜索步骤,我们可以设置 resolve.modules 属性强制只从项目的 node_modules 中查找模块

module.exports = {
    ...,
    plugins: [...],
    module: {...},
    resolve: {
        ...,
        modules: [path.resolve(__dirname, '../node_modules')],
    }
}

3.0 webpack-merge 引入webpack 公共配置

yarn add -D webpack-merge

配置 webpack.config.prod.js 文件
其中大部分的 entry,output,module 和 plugin 还有 resolve 都与开发环境的一致。可以将 webpack.base.config.js为公共配置,webpack.dev.config.js和webpack.prod.config.js根据运行环境区分

webpack.base.config.js

//webpack 是node写的, node语法
let path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ProgressBarPlugin = require('progress-bar-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

const env = process.env.NODE_ENV;
let isProd = env === 'production';

module.exports = {
    devtool: isProd ? 'none' : 'cheap-module-eval-source-map', //https://www.jianshu.com/p/62dc120d96d0
    entry: {
        main: ["@babel/polyfill", path.resolve(__dirname, '../src/index.js')], // 入口文件
        // header: path.resolve(__dirname, '../src/header.js') //多入口
    },
    output: {
        filename: '[name].[hash:8].js', // 打包后的文件名称
        path: path.resolve(__dirname, '../dist'), // 打包后的目录
        publicPath: isProd ? './' : '/' //打包后引用路径前缀 production时 "./" 
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: path.resolve(__dirname, '../public/index.html'),
            filename: "index.html",
            chunks: ['main'] // 与入口文件对应的模块名
        }),
        // new HtmlWebpackPlugin({
        //     template: path.resolve(__dirname, '../public/header.html'),
        //     filename: 'header.html',
        //     chunks: ['header'] // 与入口文件对应的模块名
        // }),
        // 打包进度
        new ProgressBarPlugin(),
    ],
    module: {
        rules: [{
                test: /\.(js|jsx)?$/, //test: /\.(ts|tsx|js|jsx)?$/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        cacheDirectory: !isProd // 缓存
                    }
                },
                include: path.resolve(__dirname, '../src'),
                exclude: /node_modules/
            },
            {
                test: /\.(css|less)$/,
                include: path.join(__dirname, '../src'),
                use: [
                    isProd ? MiniCssExtractPlugin.loader : 'style-loader',
                    {
                        loader: 'css-loader',
                        options: {
                            sourceMap: true,
                            modules: {
                                localIdentName: '[local]' //[local].[hash:8]
                            },
                        }
                    },
                    {
                        loader: 'postcss-loader',
                    },
                    {
                        loader: 'less-loader',
                        options: {
                            javascriptEnabled: true
                        }
                    }
                ]
            },
            {
                test: /\.(css|less)$/,
                include: path.join(__dirname, '../node_modules'),
                use: [
                    'style-loader',
                    'css-loader',
                    {
                        loader: 'less-loader',
                        options: {
                            javascriptEnabled: true
                        }
                    }
                ]
            },
            {
                test: /\.(jpe?g|png|gif)$/i, //图片文件
                use: [{
                    loader: 'url-loader',
                    options: {
                        limit: 10240,
                        fallback: {
                            loader: 'file-loader',
                            options: {
                                name: 'img/[name].[hash:8].[ext]'
                            }
                        }
                    }
                }]
            },
            {
                test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, //媒体文件
                use: [{
                    loader: 'url-loader',
                    options: {
                        limit: 10240,
                        fallback: {
                            loader: 'file-loader',
                            options: {
                                name: 'media/[name].[hash:8].[ext]'
                            }
                        }
                    }
                }]
            },
            {
                test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i, // 字体
                use: [{
                    loader: 'url-loader',
                    options: {
                        limit: 10240,
                        fallback: {
                            loader: 'file-loader',
                            options: {
                                name: 'fonts/[name].[hash:8].[ext]'
                            }
                        }
                    }
                }]
            },
        ]
    },
    resolve: {
        // 设置别名
        alias: {
            src: path.resolve(__dirname, '../src'),
        },
        // 设置模块查找范围
        modules: ['node_modules', path.resolve(__dirname, '../node_modules')]
    }
}
webpack.dev.config.js

let path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin')
const merge = require('webpack-merge');
const common = require('./webpack.base.config.js');
const env = process.env.NODE_ENV;
let isProd = env === 'production';
console.log('env=====', env);
console.log('isProd=====', isProd);

module.exports = merge(common, {
    mode: 'development', //模式
    devServer: {
        port: 3000, //端口号
        // host: '0.0.0.0', // 允许ip访问
        hot: true, //热更新
        compress: true, // gzip压缩
        progress: true, //进度条
        // contentBase: path.join(__dirname, '../dist'), // 服务启动访问的目录,默认为跟目录 dist/index.html
        // 解决刷新页面404,当使用 HTML5 History API 时,任意的 404 响应都可能需要被替代为 index.html。通过设置为 true 进行启用
        historyApiFallback: {
            disableDotRule: true
        },
        // 出现错误时是否在浏览器上出现遮罩层提示
        overlay: true,
        // 设置接口请求代理,更多 proxy 配置请参考 https://github.com/chimurai/http-proxy-middleware#options
        proxy: {
            '/api/': {
                changeOrigin: true, //解决跨域
                // 目标地址
                target: 'http://localhost:3000',
                // 重写路径
                pathRewrite: {
                    '^/api/': '/'
                }
            }
        }

    },
    plugins: [
        new HtmlWebpackPlugin({
            template: path.resolve(__dirname, '../public/index.html'),
            filename: "index.html",
            chunks: ['main'] // 与入口文件对应的模块名
        }),
        new webpack.HotModuleReplacementPlugin()
    ]
})
webpack.prod.config.js

const merge = require('webpack-merge');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const {
    CleanWebpackPlugin
} = require('clean-webpack-plugin');
const common = require('./webpack.base.config.js');

module.exports = merge(common, {
    mode: 'production', //生产模式
    optimization: {
        // 打包压缩js/css文件
        minimizer: [
            // 压缩 CSS 代码
            new OptimizeCssAssetsPlugin({})
        ],
    },
    plugins: [
        new CleanWebpackPlugin(),
        new MiniCssExtractPlugin({
            filename: 'css/[name].[contenthash:8].css',
            chunkFilename: 'css/[id].[contenthash:8].css'
        }),
    ],
    // 性能提醒
    performance: {
        hints: false, // 消除 文件size偏大warning等
        maxAssetSize: 30000000000, //30M 整数类型(以字节为单位)
        maxEntrypointSize: 50000000000, //入口文件最大50M 整数类型(以字节为单位)

    },
})

4 配置typescript

1)一般对应的模块都会有对应的 @types 插件可以使用。不知道的是否需要安装对应的 @types 插件的话,可以到 TypeSearch 进行查找。

 yarn add -D typescript ts-loader @types/node @types/react @types/react-dom 

2)添加 tsconfig.json 配置文件

yarn add  @types/node @types/react @types/react-dom @types/react-router-dom
tsconfig.json  
{
    "compilerOptions": {
        "experimentalDecorators": true,
        "outDir": "./dist/",
        "noImplicitAny": true,
        "suppressImplicitAnyIndexErrors": true,
        "removeComments": true,
        "esModuleInterop": true,
        "sourceMap": true,
        "module": "esnext",
        //决定如何处理模块。或者是"Node"对于Node.js/io.js,或者是"Classic"(默认)
        "moduleResolution": "node",
        "target": "esnext",
        "lib": [
            "dom",
            "es2015",
        ],
        "jsx": "react",
        "allowJs": true,
        "strict": true,
        //允许从没有设置默认导出的模块中默认导入。这并不影响代码的显示,仅为了类型检查。
        "allowSyntheticDefaultImports": true,
        "watch": true,
        "baseUrl": ".",
        "paths": {
            "@/*": [
                "./src/*"
            ]
        },
        //要包含的类型声明文件名列表;如果指定了types,只有被列出来的包才会被包含进来
        "types": [
            "react",
            "react-dom",
            "react-router-dom",
            "antd",
            "lodash"
        ]
    },
    "exclude": [
        "dist",
        "node_modules"
    ],
    "include": [
        "./src/**/*"
    ]
}

  1. 添加 .eslintrc.js文件 匹配typescript规则
yarn add  eslint-loader eslint  
module.exports = {
	env: {
		browser: true,
		es6: true
	},
	extends: [
		'airbnb',
		'plugin:@typescript-eslint/recommended',
		'plugin:import/recommended',
		'plugin:import/typescript',
		'prettier',
		'prettier/react',
		'prettier/@typescript-eslint'
	],
	plugins: ['@typescript-eslint', 'prettier'],
	parser: '@typescript-eslint/parser',
	parserOptions: {
		ecmaFeatures: {
			impliedStrict: true,
			jsx: true,
			legacyDecorators: true
		},
		ecmaVersion: 2018,
		sourceType: 'module',
		project: 'tsconfig.json'
	},
	globals: {
		__DEV__: true,
		__ENV__: true,
		__TOKEN__: true
	},
	rules: {
		'@typescript-eslint/camelcase': [
			'error',
			{
				allow: ['^UNSAFE_', 'child_process', 'drop_debugger', 'drop_console', 'keep_classnames', 'keep_fnames']
			}
		],
		'@typescript-eslint/explicit-function-return-type': 'off',

		'no-console': [
			'error',
			{
				allow: ['warn', 'error', 'info']
			}
		],
		'prefer-destructuring': [
			'error',
			{
				// 变量声明中的数组及对象解构
				VariableDeclarator: {
					array: false,
					object: true
				},
				// 赋值表达式的数组及对象解构,赋值使用解构代码不易读
				AssignmentExpression: {
					array: false,
					object: false
				}
			},
			{
				// 强制属性重命名
				enforceForRenamedProperties: false
			}
		],
		'jsx-a11y/anchor-is-valid': 'off',
		'jsx-a11y/click-events-have-key-events': 'off',
		'jsx-a11y/media-has-caption': 'warn',
		'jsx-a11y/no-noninteractive-element-interactions': 'off',
		'jsx-a11y/no-static-element-interactions': 'off',
		'import/no-extraneous-dependencies': 'off',
		'@typescript-eslint/explicit-member-accessibility': 'warn',
		'@typescript-eslint/no-unused-vars': 'off',
		'@typescript-eslint/interface-name-prefix': 'off',
		'no-restricted-syntax': 'off',
		'no-unused-expressions': 'off',
		'import/no-unresolved': 'off',
		'react/destructuring-assignment': 'warn',
		'react/jsx-filename-extension': [
			'error',
			{
				extensions: ['.js', '.jsx', '.ts', '.tsx']
			}
		],
		'react/prefer-stateless-function': 'off',
		// 使用 TS 无需使用 prop-types
		'react/prop-types': 'off',

		'prettier/prettier': 'off'
	},
	settings: {
		'import/resolver': {
			alias: {
				extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'],
				map: [['@', './src']]
			}
		}
	}
};

4)在 webpack 中添加 TypeScript 的 rules 配置

yarn add -D eslint-formatter-friendly 
webpack.config.base.js

import friendlyFormatter from 'eslint-formatter-friendly';

module.exports = {
    ...
    rules: [
        {
                enforce: 'pre', // 强制放在最前面执行
                test: /\.(ts|tsx|js)?$/,
                include: path.resolve(__dirname, '../src'),
                exclude: /node_modules/,
                use: {
                    loader: 'eslint-loader',
                    options: {
                        cache: true, // 缓存lint结果,可以减少lint时间
                        formatter: friendlyFormatter
                    }
                }
            }
        ...
    ],
    ...
}

5 配置react+mobx+router

6. 进一步优化

6.1 @babel/polyfill
上面的babel-loader只会将 ES6/7/8语法转换为ES5语法,但是对新api并不会转换 例如(promise、Generator、Set、Maps、Proxy等) 此时我们需要借助babel-polyfill来帮助我们转换 可以让我们愉快的使用浏览器不兼容的es6+的新API。但是他有几个缺点:

  • 一是我们只是用了几个API,它却整个的引入了
  • 二是会污染全局

接下来我们做一下优化,添加

yarn add @babel/plugin-transform-runtime -D
yarn add core-js@2.6.5 -D
yarn add @babel/plugin-proposal-class-properties -D

yarn add @babel/runtime-corejs2 -S

添加完后配置page.json,添加browserslist,来声明生效浏览器

"browserslist": [
    "> 1%",
    "last 2 versions"
  ],

在修改我们的babel配置文件

{
    presets: [["@babel/preset-env",{
        useBuiltIns: "entry",// entry
        corejs: 2
    }], "@babel/preset-react"],
    plugins: ["@babel/plugin-syntax-dynamic-import",'@babel/plugin-transform-runtime','@babel/plugin-proposal-class-properties']
}

useBuiltIns是关键属性,它会根据 browserlist 是否转换新语法与 polyfill 新 AP业务代码使用到的新 API 按需进行 polyfill

false : 不启用polyfill, 如果 import '@babel/polyfill', 会无视 browserlist 将所有的 polyfill 加载进来  
entry : 启用,需要手动 import '@babel/polyfill', 这样会根据 browserlist 过滤出 需要的 polyfill ,根据浏览器版本的支持,将 polyfill 需求拆分引入,仅引入有浏览器不支持的polyfill  
usage : 不需要手动import '@babel/polyfill'(加上也无妨,构造时会去掉), 且会根据 browserlist +  
注:经测试usage无法支持IE,推荐使用entry,虽然会大几十K。
@babel/plugin-transform-runtime和@babel/runtime-corejs2,前者是开发时候使用,后者是生产环境使用。主要功能:避免多次编译出helper函数:Babel转移后的代码想要实现和原来代码一样的功能需要借助一些帮助函数。还可以解决@babel/polyfill提供的类或者实例方法污染全局作用域的情况。

6.2 UglifyJsPlugin压缩js
splitchunks

webpack.prod.config.js

const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const merge = require('webpack-merge');
const common = require('./webpack.base.config.js');

module.exports = merge(common,{
    optimization: {
        // 打包压缩js/css文件
        minimizer: [
            new UglifyJsPlugin({
                uglifyOptions: {
                    compress: {
                        // 在UglifyJs删除没有用到的代码时不输出警告
                        warnings: false,
                        // 删除所有的 `console` 语句,可以兼容ie浏览器
                        drop_console: true,
                        // 内嵌定义了但是只用到一次的变量
                        collapse_vars: true,
                        // 提取出出现多次但是没有定义成变量去引用的静态值
                        reduce_vars: true,
                    },
                    output: {
                        // 最紧凑的输出
                        beautify: false,
                        // 删除所有的注释
                        comments: false,
                    }
                }
            }),
            new OptimizeCSSAssetsPlugin({})
        ],
        splitChunks: {
            chunks:'all',
            cacheGroups: {
                styles: {
                    name: 'styles',
                    test: /\.(css|less)/,
                    chunks: 'all',
                    enforce: true,
                    reuseExistingChunk: true // 表示是否使用已有的 chunk,如果为 true 则表示如果当前的 chunk 包含的模块已经被抽取出去了,那么将不会重新生成新的。
                },
                commons: {
                    name: 'commons',
                    chunks: 'initial',
                    minChunks: 2,
                    reuseExistingChunk: true
                },
                vendors: {
                    name: 'vendors',
                    test: /[\\/]node_modules[\\/]/,
                    priority: -10,
                    reuseExistingChunk: true
                }
            }
        },
        runtimeChunk: true
    })
}

4.3 提取公共代码

我们打包的文件里面包含了react,react-router-dom,react-router,mobx等等这些公共依赖,每次发布都要重新加载,其实没必要,我们可以将他们单独提取出来。在webpack.dev.config.js中配置入口:

entry: {
    app:[
        path.join(__dirname, '../src/index.js')
    ],
    vendor: ['react', 'react-router-dom', 'react-dom', 'mobx']//公共依赖
},
output: {
    path: path.join(__dirname, '../dist'),
    filename: '[name].[hash].js',
    chunkFilename: '[name].[chunkhash].js'
},