webpack(4) 和 babel 总结

108 阅读15分钟

Vue 基础 —— webpack(4) 和 babel

webpack基本配置:

package.json文件配置:

{
  "name": "07-webpack-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo "Error: no test specified" && exit 1",
    "devBuild": "webpack --config build-optimization/webpack.dev.js",
    "dev": "webpack-dev-server --config build-optimization/webpack.dev.js",
    "build": "webpack --config build-optimization/webpack.prod.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.7.4",
    "@babel/preset-env": "^7.7.4",
    "autoprefixer": "^9.7.3",
    "babel-loader": "^8.0.6",
    "clean-webpack-plugin": "^3.0.0",
    "css-loader": "^3.2.1",
    "file-loader": "^5.0.2",
    "happypack": "^5.0.1",
    "html-webpack-plugin": "^3.2.0",
    "less": "^3.10.3",
    "less-loader": "^5.0.0",
    "mini-css-extract-plugin": "^0.8.0",
    "optimize-css-assets-webpack-plugin": "^5.0.3",
    "postcss-loader": "^3.0.0",
    "style-loader": "^1.0.1",
    "terser-webpack-plugin": "^2.2.2",
    "url-loader": "^3.0.0",
    "webpack": "^4.41.2",
    "webpack-cli": "^3.3.10",
    "webpack-dev-server": "^3.9.0",
    "webpack-merge": "^4.2.2",
    "webpack-parallel-uglify-plugin": "^1.1.2"
  },
  "dependencies": {
    "lodash": "^4.17.15",
    "moment": "^2.24.0"
  }
}
​

配置拆分 及 merge:

webpack.common.js:

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { srcPath, distPath } = require('./paths')
​
module.exports = {
    entry: path.join(srcPath, 'index'),
    module: {
        rules: [
            // 处理 ES6
            {
                test: /.js$/, // 需要处理的文件规则
                loader: ['babel-loader'],
                include: srcPath, // 包含的文件夹
                exclude: /node_modules/ // 排除的文件夹
            },
            // {
            //     test: /.vue$/,
            //     loader: ['vue-loader'],
            //     include: srcPath
            // },
            // {
            //     test: /.css$/,
            //     // loader 的执行顺序是:从后往前(知识点)
            //     loader: ['style-loader', 'css-loader']
            // },
            {
                test: /.css$/,
                // loader 的执行顺序是:从后往前
                loader: ['style-loader', 'css-loader', 'postcss-loader'] // 加了 postcss 做浏览器的兼容性
            },
            {
                test: /.less$/,
                // 增加 'less-loader' ,注意顺序
                loader: ['style-loader', 'css-loader', 'less-loader']
            }
        ]
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: path.join(srcPath, 'index.html'),
            filename: 'index.html'
        })
    ]
}

考点:loader 的执行顺序是:从后往前

loader: ['style-loader', 'css-loader'] 
// 先执行 css-loader 再执行 style-loader

注意:使用 post-css的时候,需要配置 post-css

前提:需要安装 postcss-loader 和 autoprefixer

postcss.config.js:

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

注意:使用 babel-loader 的时候,需要配置 babel

前提:安装 babel-preset-env、babel-cli、babel-core、babel-node

前提:安装 babel-polypill

.babelIrc

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

webpack.dev.js:

const path = require('path')
const webpack = require('webpack')
const webpackCommonConf = require('./webpack.common.js')
const { smart } = require('webpack-merge')
const { srcPath, distPath } = require('./paths')
​
module.exports = smart(webpackCommonConf, {
    mode: 'development',
    module: {
        rules: [
            // 直接引入图片 url
            {
                test: /.(png|jpg|jpeg|gif)$/,
                use: 'file-loader'
            }
        ]
    },
    plugins: [
        new webpack.DefinePlugin({
            // window.ENV = 'development'
            ENV: JSON.stringify('development')
        })
    ],
    // 在 dev 环境下启动服务
    devServer: {
        port: 8080,
        progress: true,  // 显示打包的进度条
        contentBase: distPath,  // 根目录
        open: true,  // 自动打开浏览器
        compress: true,  // 启动 gzip 压缩
​
        // 设置代理
        // 在 webpack 下如何跨域请求端口
        proxy: {
            // 将本地 /api/xxx 代理到 localhost:3000/api/xxx
            '/api': 'http://localhost:3000',
​
            // 将本地 /api2/xxx 代理到 localhost:3000/xxx
            '/api2': {
                target: 'http://localhost:3000',
                pathRewrite: {
                    '/api2': ''
                }
            }
        }
    }
})

考点:在 webpak 下,如何跨域请求端口

使用代理:

proxy: {
            // 将本地 /api/xxx 代理到 localhost:3000/api/xxx
            '/api': 'http://localhost:3000',
​
            // 将本地 /api2/xxx 代理到 localhost:3000/xxx
            '/api2': {
                target: 'http://localhost:3000',
                pathRewrite: {
                    '/api2': ''
                }
            }
        }

Web pack.prod.js:

const path = require('path')
const webpack = require('webpack')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const webpackCommonConf = require('./webpack.common.js')
const { smart } = require('webpack-merge')
const { srcPath, distPath } = require('./paths')
​
module.exports = smart(webpackCommonConf, {
    mode: 'production',
    output: {
        filename: 'bundle.[contentHash:8].js',  // 打包代码时,加上 hash 戳(内容变了,Hash 值就变化,否则就不变 —— 缓存)
        path: distPath,
        // publicPath: 'http://cdn.abc.com'  // 修改所有静态文件 url 的前缀(如 cdn 域名),这里暂时用不到
    },
    module: {
        rules: [
            // 图片 - 考虑 base64 编码的情况 减少网络请求
            {
                test: /.(png|jpg|jpeg|gif)$/,
                use: {
                    loader: 'url-loader',
                    options: {
                        // 小于 5kb 的图片用 base64 格式产出
                        // 否则,依然延用 file-loader 的形式,产出 url 格式
                        limit: 5 * 1024,
​
                        // 打包到 img 目录下
                        outputPath: '/img1/',
​
                        // 设置图片的 cdn 地址(也可以统一在外面的 output 中设置,那将作用于所有静态资源)
                        // publicPath: 'http://cdn.abc.com'
                    }
                }
            },
        ]
    },
    plugins: [
        new CleanWebpackPlugin(), // 会默认清空 output.path 文件夹
        new webpack.DefinePlugin({
            // window.ENV = 'production'
            ENV: JSON.stringify('production')
        })
    ]
})


webpack的高级配置

多入口:

- 在 webpack.common.js 中,建立多个入口;
  index
  other
​
- 在 webpack.prod.js 中,修改 output
  filename:'[name].[contentHash:8].js'
  
- 除了 .js 的文件,还有 .html 文件

建立多个入口:

JS 文件:entry

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { srcPath, distPath } = require('./paths')
​
module.exports = {
    entry: {
        index: path.join(srcPath, 'index.js'),
        other: path.join(srcPath, 'other.js')
    },
    module: {
        rules: [
            // 处理 ES6
            {
                test: /.js$/, // 需要处理的文件规则
                loader: ['babel-loader'],
                include: srcPath, // 包含的文件夹
                exclude: /node_modules/ // 排除的文件夹
            },
            // {
            //     test: /.vue$/,
            //     loader: ['vue-loader'],
            //     include: srcPath
            // },
            // {
            //     test: /.css$/,
            //     // loader 的执行顺序是:从后往前(知识点)
            //     loader: ['style-loader', 'css-loader']
            // },
            {
                test: /.css$/,
                // loader 的执行顺序是:从后往前
                loader: ['style-loader', 'css-loader', 'postcss-loader'] // 加了 postcss 做浏览器的兼容性
            },
            {
                test: /.less$/,
                // 增加 'less-loader' ,注意顺序
                loader: ['style-loader', 'css-loader', 'less-loader']
            }
        ]
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: path.join(srcPath, 'index.html'),
            filename: 'index.html'
        })
    ]
}

HTML 文件:HtmlWebpackPlugin

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { srcPath, distPath } = require('./paths')
​
module.exports = {
    entry: {
        index: path.join(srcPath, 'index.js'),
        other: path.join(srcPath, 'other.js')
    },
    module: {
        rules: [
            // 处理 ES6
            {
                test: /.js$/, // 需要处理的文件规则
                loader: ['babel-loader'],
                include: srcPath, // 包含的文件夹
                exclude: /node_modules/ // 排除的文件夹
            },
            // {
            //     test: /.vue$/,
            //     loader: ['vue-loader'],
            //     include: srcPath
            // },
            // {
            //     test: /.css$/,
            //     // loader 的执行顺序是:从后往前(知识点)
            //     loader: ['style-loader', 'css-loader']
            // },
            {
                test: /.css$/,
                // loader 的执行顺序是:从后往前
                loader: ['style-loader', 'css-loader', 'postcss-loader'] // 加了 postcss 做浏览器的兼容性
            },
            {
                test: /.less$/,
                // 增加 'less-loader' ,注意顺序
                loader: ['style-loader', 'css-loader', 'less-loader']
            }
        ]
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: path.join(srcPath, 'index.html'),
            filename: 'index.html',
            // chunks 表示该页面要引用哪些 JS 文件
            // 不写的话。会将所有的 JS 文件
            chunks: ['index'] // 只引用 index.js
        }),
        new HtmlWebpackPlugin({
            template: path.join(srcPath, 'other.html'),
            filename: 'other.html',
            chunks: ['other'] // 只引用 other.js
        })
    ]
}

修改 output:filename

const path = require('path')
const webpack = require('webpack')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const webpackCommonConf = require('./webpack.common.js')
const { smart } = require('webpack-merge')
const { srcPath, distPath } = require('./paths')
​
module.exports = smart(webpackCommonConf, {
    mode: 'production',
    output: {
        filename: '[name].[contentHash:8].js',  // 打包代码时,加上 hash 戳(内容变了,Hash 值就变化,否则就不变 —— 缓存)
        path: distPath,
        // publicPath: 'http://cdn.abc.com'  // 修改所有静态文件 url 的前缀(如 cdn 域名),这里暂时用不到
    },
    module: {
        rules: [
            // 图片 - 考虑 base64 编码的情况 减少网络请求
            {
                test: /.(png|jpg|jpeg|gif)$/,
                use: {
                    loader: 'url-loader',
                    options: {
                        // 小于 5kb 的图片用 base64 格式产出
                        // 否则,依然延用 file-loader 的形式,产出 url 格式
                        limit: 5 * 1024,
​
                        // 打包到 img 目录下
                        outputPath: '/img1/',
​
                        // 设置图片的 cdn 地址(也可以统一在外面的 output 中设置,那将作用于所有静态资源)
                        // publicPath: 'http://cdn.abc.com'
                    }
                }
            },
        ]
    },
    plugins: [
        new CleanWebpackPlugin(), // 会默认清空以往的 output.path 文件夹
        new webpack.DefinePlugin({
            // window.ENV = 'production'
            ENV: JSON.stringify('production')
        })
    ]
})

抽离 CSS 文件: 通过 外链 的形式引入,不再在 JS 文件中存放

- 在线上必须将 CSS 文件进行抽离,并进行压缩!避免代码体积过大
- 在 webpack.common.js 中,将 CSS 的处理删除;- 在 webpack.dev.js 中,沿用之前的 CSS 处理;- 安装 MiniCssExtractPlugin 插件,并在 webpack.prod.js 中引用,抽离 css;- 安装 TerserJSPlugin 和 OptimizeCSSAssetsPlugin 两个插件,并在webpack.prod.js 中使用,压缩 css

webpack.common.js:

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { srcPath, distPath } = require('./paths')
​
module.exports = {
    entry: {
        index: path.join(srcPath, 'index.js'),
        other: path.join(srcPath, 'other.js')
    },
    module: {
        rules: [
            // 处理 ES6
            {
                test: /.js$/, // 需要处理的文件规则
                loader: ['babel-loader'],
                include: srcPath, // 包含的文件夹
                exclude: /node_modules/ // 排除的文件夹
            }
        ]
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: path.join(srcPath, 'index.html'),
            filename: 'index.html',
            // chunks 表示该页面要引用哪些 JS 文件
            // 不写的话。会将所有的 JS 文件
            chunks: ['index'] // 只引用 index.js
        }),
        new HtmlWebpackPlugin({
            template: path.join(srcPath, 'other.html'),
            filename: 'other.html',
            chunks: ['other'] // 只引用 other.js
        })
    ]
}

webpack.dev.js:

const path = require('path')
const webpack = require('webpack')
const webpackCommonConf = require('./webpack.common.js')
const { smart } = require('webpack-merge')
const { srcPath, distPath } = require('./paths')
​
module.exports = smart(webpackCommonConf, {
    mode: 'development',
    module: {
        rules: [
            // 直接引入图片 url
            {
                test: /.(png|jpg|jpeg|gif)$/,
                use: 'file-loader'
            },
            // {
            //     test: /.css$/,
            //     // loader 的执行顺序是:从后往前
            //     loader: ['style-loader', 'css-loader']
            // },
            {
                test: /.css$/,
                // loader 的执行顺序是:从后往前
                loader: ['style-loader', 'css-loader', 'postcss-loader'] // 加了 postcss
            },
            {
                test: /.less$/,
                // 增加 'less-loader' ,注意顺序
                loader: ['style-loader', 'css-loader', 'less-loader']
            }
        ]
    },
    plugins: [
        new webpack.DefinePlugin({
            // window.ENV = 'production'
            ENV: JSON.stringify('development')
        })
    ],
    devServer: {
        port: 8080,
        progress: true,  // 显示打包的进度条
        contentBase: distPath,  // 根目录
        open: true,  // 自动打开浏览器
        compress: true,  // 启动 gzip 压缩
​
        // 设置代理
        proxy: {
            // 将本地 /api/xxx 代理到 localhost:3000/api/xxx
            '/api': 'http://localhost:3000',
​
            // 将本地 /api2/xxx 代理到 localhost:3000/xxx
            '/api2': {
                target: 'http://localhost:3000',
                pathRewrite: {
                    '/api2': ''
                }
            }
        }
    }
})

webpack.prod.js:

const path = require('path')
const webpack = require('webpack')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const TerserJSPlugin = require('terser-webpack-plugin')
const OptimizeAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const webpackCommonConf = require('./webpack.common.js')
const { smart } = require('webpack-merge')
const { srcPath, distPath } = require('./paths')
​
module.exports = smart(webpackCommonConf, {
    mode: 'production',
    output: {
        filename: '[name].[contentHash:8].js',  // 打包代码时,加上 hash 戳(内容变了,Hash 值就变化,否则就不变 —— 缓存)
        path: distPath,
        // publicPath: 'http://cdn.abc.com'  // 修改所有静态文件 url 的前缀(如 cdn 域名),这里暂时用不到
    },
    module: {
        rules: [
            // 图片 - 考虑 base64 编码的情况 减少网络请求
            {
                test: /.(png|jpg|jpeg|gif)$/,
                use: {
                    loader: 'url-loader',
                    options: {
                        // 小于 5kb 的图片用 base64 格式产出
                        // 否则,依然延用 file-loader 的形式,产出 url 格式
                        limit: 5 * 1024,
​
                        // 打包到 img 目录下
                        outputPath: '/img1/',
​
                        // 设置图片的 cdn 地址(也可以统一在外面的 output 中设置,那将作用于所有静态资源)
                        // publicPath: 'http://cdn.abc.com'
                    }
                }
            },
            // 抽离 css
            {
              test: /.css$/,
              loader: [
                // 注意:这里不再用 style-loader
                MiniCssExtractPlugin.loader,
                'css-loader',
                'postcss-loader'
              ]
            },
            // 抽离 less -> css
            {
              test: /.less$/,
              loader: [
                // 注意:这里不再用 style-loader
                MiniCssExtractPlugin.loader,
                'css-loader',
                'less-loader',
                'postcss-loader'
              ]
            }
        ]
    },
    plugins: [
        new CleanWebpackPlugin(), // 会默认清空以往的 output.path 文件夹
        new webpack.DefinePlugin({
            // window.ENV = 'production'
            ENV: JSON.stringify('production')
        }),
        // 抽离 css 文件
        new MiniCssExtractPlugin({
          filename: 'css/main.[contentHash:8].css'
        })
    ],
  
    optimization: {
      // 压缩 css
      minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})]
    }
})

关键代码:

// 安装 MiniCssExtractPlugin 插件
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
// 抽离 css
            {
              test: /.css$/,
              loader: [
                // 注意:这里不再用 style-loader
                MiniCssExtractPlugin.loader,
                'css-loader',
                'postcss-loader'
              ]
            },
            // 抽离 less -> css
            {
              test: /.less$/,
              loader: [
                // 注意:这里不再用 style-loader
                MiniCssExtractPlugin.loader,
                'css-loader',
                'less-loader',
                'postcss-loader'
              ]
            }
plugins: [
        // 抽离 css 文件
        new MiniCssExtractPlugin({
          filename: 'css/main.[contentHash:8].css'
        })
    ]
// 安装 TerserJSPlugin 和 OptimizeCssAssetsPlugin 插件
const TerserJSPlugin = require('terser-webpack-plugin')
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin')
// 压缩 css
optimization: {
      // 压缩 css
      minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})]
    }

抽离 公共代码 和 第三方代码:

webpack.prod.js:

const path = require('path')
const webpack = require('webpack')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const TerserJSPlugin = require('terser-webpack-plugin')
const OptimizeAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const webpackCommonConf = require('./webpack.common.js')
const { smart } = require('webpack-merge')
const { srcPath, distPath } = require('./paths')
​
module.exports = smart(webpackCommonConf, {
    mode: 'production',
    output: {
        filename: '[name].[contentHash:8].js',  // 打包代码时,加上 hash 戳(内容变了,Hash 值就变化,否则就不变 —— 缓存)
        path: distPath,
        // publicPath: 'http://cdn.abc.com'  // 修改所有静态文件 url 的前缀(如 cdn 域名),这里暂时用不到
    },
    module: {
        rules: [
            // 图片 - 考虑 base64 编码的情况 减少网络请求
            {
                test: /.(png|jpg|jpeg|gif)$/,
                use: {
                    loader: 'url-loader',
                    options: {
                        // 小于 5kb 的图片用 base64 格式产出
                        // 否则,依然延用 file-loader 的形式,产出 url 格式
                        limit: 5 * 1024,
​
                        // 打包到 img 目录下
                        outputPath: '/img1/',
​
                        // 设置图片的 cdn 地址(也可以统一在外面的 output 中设置,那将作用于所有静态资源)
                        // publicPath: 'http://cdn.abc.com'
                    }
                }
            },
            // 抽离 css
            {
              test: /.css$/,
              loader: [
                // 注意:这里不再用 style-loader
                MiniCssExtractPlugin.loader,
                'css-loader',
                'postcss-loader'
              ]
            },
            // 抽离 less -> css
            {
              test: /.less$/,
              loader: [
                // 注意:这里不再用 style-loader
                MiniCssExtractPlugin.loader,
                'css-loader',
                'less-loader',
                'postcss-loader'
              ]
            }
        ]
    },
    plugins: [
        new CleanWebpackPlugin(), // 会默认清空以往的 output.path 文件夹
        new webpack.DefinePlugin({
            // window.ENV = 'production'
            ENV: JSON.stringify('production')
        }),
        // 抽离 css 文件
        new MiniCssExtractPlugin({
          filename: 'css/main.[contentHash:8].css'
        })
    ],
  
    optimization: {
      // 压缩 css
      minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})]
      
      // 分割代码块
      splitChunks: {
        /*
          initial:入口 chunk,对于 异步导入 的文件不处理
          async:异步 chunk,只针对 异步导入 的文件进行处理
          all:全部 chunk
        */
        chunks: 'all',
      
        // 缓存分组
        cacheGroups: {
          // 第三方模块
          vendor: {
            name: 'vendor', // chunk 名称
            priority: 1, // 权限更高,优先抽离,重要!!!
            test: /node_modules/,
            minSize: 0, // 大小限制
            minChunks: 1 // 最少复用过几次
          },
          
          // 公共的模块
          common: {
            name: 'common', // chunk 名称
            priority: 0, // 优先级
            minSize: 0, // 公共模块的大小限制
            minChunks: 2 // 公共模块最少复用过几次
          }
        }
      }
    }
})

关键代码:

optimization: {
      // 分割代码块
      splitChunks: {
        /*
          initial:入口 chunk,对于 异步导入 的文件不处理
          async:异步 chunk,只针对 异步导入 的文件进行处理
          all:全部 chunk
        */
        chunks: 'all',
      
        // 缓存分组
        cacheGroups: {
          // 第三方模块
          vendor: {
            name: 'vendor', // chunk 名称
            priority: 1, // 权限更高,优先抽离,重要!!!
            test: /node_modules/,
            minSize: 5KB, // 大小限制
            minChunks: 1 // 最少复用过几次
          },
          
          // 公共的模块
          common: {
            name: 'common', // chunk 名称
            priority: 0, // 优先级
            minSize: 3KB, // 公共模块的大小限制
            minChunks: 2 // 公共模块最少复用过几次
          }
        }
      }
    }

webpack.common.js:

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { srcPath, distPath } = require('./paths')
​
module.exports = {
    entry: {
        index: path.join(srcPath, 'index.js'),
        other: path.join(srcPath, 'other.js')
    },
    module: {
        rules: [
            // 处理 ES6
            {
                test: /.js$/, // 需要处理的文件规则
                loader: ['babel-loader'],
                include: srcPath, // 包含的文件夹
                exclude: /node_modules/ // 排除的文件夹
            }
        ]
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: path.join(srcPath, 'index.html'),
            filename: 'index.html',
            // chunks 表示该页面要引用哪些 JS 文件
            // 不写的话。会将所有的 JS 文件
            chunks: ['index', 'vendor', 'common'] // 考虑代码分割
        }),
        new HtmlWebpackPlugin({
            template: path.join(srcPath, 'other.html'),
            filename: 'other.html',
            chunks: ['other', 'common'] // 考虑代码分割
        })
    ]
}

关键代码:考虑代码分割:

plugins: [
        new HtmlWebpackPlugin({
            template: path.join(srcPath, 'index.html'),
            filename: 'index.html',
            // chunks 表示该页面要引用哪些 JS 文件
            // 不写的话。会将所有的 JS 文件
            chunks: ['index', 'vendor', 'common'] // 考虑代码分割
        }),
        new HtmlWebpackPlugin({
            template: path.join(srcPath, 'other.html'),
            filename: 'other.html',
            chunks: ['other', 'common'] // 考虑代码分割
        })
    ]

懒加载: 引入 动态数据

Dynamic-data.js:

export default {
  message: 'this is dynamic data'
}

Index.js:

setTimeout(() => {
  // 回顾 Vue 的 异步组件
  // 也会 定义一个 chunk
  import('./dynamic-data.js').then(res => {
    console.log(res.default.message) // 注意这里的 default
  })
})
- 也会产出一个 chunk

无需 webpack 做什么配置,内置支持!!!


处理 Vue:

// 安装 vue-loader
npm install vue-loader

webpack.common.js:

module.exports = {
    entry: path.join(srcPath, 'index'),
    module: {
        rules: [
            {
                test: /.vue$/,
                loader: ['vue-loader'],
                include: srcPath
            }
    }


module、chunk、bundle 的区别:

- module:各个源码文件,webpack 中一切皆模块;- chunk:多模块合并成的,如:entry、import()、splitChunk;- bundle:最终的输出文件


webpack 性能优化:

目的:

- 优化打包构建速度(开发体验和效率);- 优化产出代码(产品性能)

优化构建速度:

优化 babel-loader:

{
  test: /.js$/,
  use: ['babel-loader?cacheDirectory'], // 开启缓存
  include: path.resolve(__dirname, 'src'), // 明确范围
  // exclude: path.resolve(__dirname, 'node_modules') // 排除范围
  // include 和 exclude 两者选一个即可
}

IgnorePlugin:

- webpack 的 内置插件;- 用于 忽略无用的文件

案例:

  • import moment from 'moment' 是一个 格式化日期/时间 的库,支持多种语言;
  • 默认会引入所有语言的 JS 代码,导致代码体积过大;
  • 需要只引入中文
npm install moment

Index.js:手动引入需要的语言

import moment from 'moment'
import 'moment.locale/zh-cn' // 手动引入中文语言包
moment.locale('zh-cn') // 设置语言为中文
console.log('locale', moment.locale()) // 获取语言
console.log('date', moment().format('ll')) // 获取日期

webpack.prod.js:使用 IgnorePlugin 插件

const path = require('path')
const webpack = require('webpack')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const TerserJSPlugin = require('terser-webpack-plugin')
const OptimizeAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const webpackCommonConf = require('./webpack.common.js')
const { smart } = require('webpack-merge')
const { srcPath, distPath } = require('./paths')
​
module.exports = smart(webpackCommonConf, {
    mode: 'production',
    output: {
        filename: '[name].[contentHash:8].js',  // 打包代码时,加上 hash 戳(内容变了,Hash 值就变化,否则就不变 —— 缓存)
        path: distPath,
        // publicPath: 'http://cdn.abc.com'  // 修改所有静态文件 url 的前缀(如 cdn 域名),这里暂时用不到
    },
    module: {
        rules: [
            // 图片 - 考虑 base64 编码的情况 减少网络请求
            {
                test: /.(png|jpg|jpeg|gif)$/,
                use: {
                    loader: 'url-loader',
                    options: {
                        // 小于 5kb 的图片用 base64 格式产出
                        // 否则,依然延用 file-loader 的形式,产出 url 格式
                        limit: 5 * 1024,
​
                        // 打包到 img 目录下
                        outputPath: '/img1/',
​
                        // 设置图片的 cdn 地址(也可以统一在外面的 output 中设置,那将作用于所有静态资源)
                        // publicPath: 'http://cdn.abc.com'
                    }
                }
            },
            // 抽离 css
            {
              test: /.css$/,
              loader: [
                // 注意:这里不再用 style-loader
                MiniCssExtractPlugin.loader,
                'css-loader',
                'postcss-loader'
              ]
            },
            // 抽离 less -> css
            {
              test: /.less$/,
              loader: [
                // 注意:这里不再用 style-loader
                MiniCssExtractPlugin.loader,
                'css-loader',
                'less-loader',
                'postcss-loader'
              ]
            }
        ]
    },
    plugins: [
        new CleanWebpackPlugin(), // 会默认清空以往的 output.path 文件夹
        new webpack.DefinePlugin({
            // window.ENV = 'production'
            ENV: JSON.stringify('production')
        }),
        // 抽离 css 文件
        new MiniCssExtractPlugin({
          filename: 'css/main.[contentHash:8].css'
        }),
      
        // 忽略 moment 下的 /locale 目录
        new webpack.IgnorePlugin(/./locale/, /moment//)
    ],
  
    optimization: {
      // 压缩 css
      minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})]
      
      // 分割代码块
      splitChunks: {
        /*
          initial:入口 chunk,对于 异步导入 的文件不处理
          async:异步 chunk,只针对 异步导入 的文件进行处理
          all:全部 chunk
        */
        chunks: 'all',
      
        // 缓存分组
        cacheGroups: {
          // 第三方模块
          vendor: {
            name: 'vendor', // chunk 名称
            priority: 1, // 权限更高,优先抽离,重要!!!
            test: /node_modules/,
            minSize: 0, // 大小限制
            minChunks: 1 // 最少复用过几次
          },
          
          // 公共的模块
          common: {
            name: 'common', // chunk 名称
            priority: 0, // 优先级
            minSize: 0, // 公共模块的大小限制
            minChunks: 2 // 公共模块最少复用过几次
          }
        }
      }
    }
})

关键代码:

// 忽略 moment 下的 /locale 目录
new webpack.IgnorePlugin(/./locale/, /moment//)

noParse:

- 避免重复打包
module.exports = {
  module: {
    // 单独完整的 'vue.min.js' 文件已经采用模块化处理过
    // 忽略对 'vue.min.js' 文件的递归解析处理
    noParse: [/vue.min.js$/]
  }
}

IgnorePlugin 和 noParse 的区别:

- IgnorePlugin:直接不引入,代码中没有;- noParse:引入,但不打包

happyPack:

- JS 单线程,happyPack 开启 多进程打包 更快;- 提高构建速度(特别是 多核CPU);- 需要改变 babel-loader 的配置
npm install happyPack

webpack.common.js:删除 babel-loader 的配置

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { srcPath, distPath } = require('./paths')
​
module.exports = {
    entry: {
        index: path.join(srcPath, 'index.js'),
        other: path.join(srcPath, 'other.js')
    },
    module: {
        rules: [
            // babel-loader
        ]
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: path.join(srcPath, 'index.html'),
            filename: 'index.html',
            // chunks 表示该页面要引用哪些 JS 文件
            // 不写的话。会将所有的 JS 文件
            chunks: ['index', 'vendor', 'common'] // 考虑代码分割
        }),
        new HtmlWebpackPlugin({
            template: path.join(srcPath, 'other.html'),
            filename: 'other.html',
            chunks: ['other', 'common'] // 考虑代码分割
        })
    ]
}

webpack.dev.js:将原来在 common 中的 babel-loader 配置拷贝

const path = require('path')
const webpack = require('webpack')
const webpackCommonConf = require('./webpack.common.js')
const { smart } = require('webpack-merge')
const { srcPath, distPath } = require('./paths')
​
module.exports = smart(webpackCommonConf, {
    mode: 'development',
    module: {
        rules: [
            // 处理 ES6
            {
                test: /.js$/, // 需要处理的文件规则
                loader: ['babel-loader'],
                include: srcPath, // 包含的文件夹
                exclude: /node_modules/ // 排除的文件夹
            },
            // 直接引入图片 url
            {
                test: /.(png|jpg|jpeg|gif)$/,
                use: 'file-loader'
            },
            // {
            //     test: /.css$/,
            //     // loader 的执行顺序是:从后往前
            //     loader: ['style-loader', 'css-loader']
            // },
            {
                test: /.css$/,
                // loader 的执行顺序是:从后往前
                loader: ['style-loader', 'css-loader', 'postcss-loader'] // 加了 postcss
            },
            {
                test: /.less$/,
                // 增加 'less-loader' ,注意顺序
                loader: ['style-loader', 'css-loader', 'less-loader']
            }
        ]
    },
    plugins: [
        new webpack.DefinePlugin({
            // window.ENV = 'production'
            ENV: JSON.stringify('development')
        })
    ],
    devServer: {
        port: 8080,
        progress: true,  // 显示打包的进度条
        contentBase: distPath,  // 根目录
        open: true,  // 自动打开浏览器
        compress: true,  // 启动 gzip 压缩
​
        // 设置代理
        proxy: {
            // 将本地 /api/xxx 代理到 localhost:3000/api/xxx
            '/api': 'http://localhost:3000',
​
            // 将本地 /api2/xxx 代理到 localhost:3000/xxx
            '/api2': {
                target: 'http://localhost:3000',
                pathRewrite: {
                    '/api2': ''
                }
            }
        }
    }
})

webpack.prod.js:

const path = require('path')
const webpack = require('webpack')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const TerserJSPlugin = require('terser-webpack-plugin')
const OptimizeAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const HappyPack = require('happyPack')
const webpackCommonConf = require('./webpack.common.js')
const { smart } = require('webpack-merge')
const { srcPath, distPath } = require('./paths')
​
module.exports = smart(webpackCommonConf, {
    mode: 'production',
    output: {
        filename: '[name].[contentHash:8].js',  // 打包代码时,加上 hash 戳(内容变了,Hash 值就变化,否则就不变 —— 缓存)
        path: distPath,
        // publicPath: 'http://cdn.abc.com'  // 修改所有静态文件 url 的前缀(如 cdn 域名),这里暂时用不到
    },
    module: {
        rules: [
            // JS
            {
              test: /.js$/,
              // 将对 .js 文件的处理转交给 id 为 babel 的 HappyPack 实例
              use: ['happypack/loader?id=babel'],
              include: srcPath,
              // exclude: /node_module/
            }
            // 图片 - 考虑 base64 编码的情况 减少网络请求
            {
                test: /.(png|jpg|jpeg|gif)$/,
                use: {
                    loader: 'url-loader',
                    options: {
                        // 小于 5kb 的图片用 base64 格式产出
                        // 否则,依然延用 file-loader 的形式,产出 url 格式
                        limit: 5 * 1024,
​
                        // 打包到 img 目录下
                        outputPath: '/img1/',
​
                        // 设置图片的 cdn 地址(也可以统一在外面的 output 中设置,那将作用于所有静态资源)
                        // publicPath: 'http://cdn.abc.com'
                    }
                }
            },
            // 抽离 css
            {
              test: /.css$/,
              loader: [
                // 注意:这里不再用 style-loader
                MiniCssExtractPlugin.loader,
                'css-loader',
                'postcss-loader'
              ]
            },
            // 抽离 less -> css
            {
              test: /.less$/,
              loader: [
                // 注意:这里不再用 style-loader
                MiniCssExtractPlugin.loader,
                'css-loader',
                'less-loader',
                'postcss-loader'
              ]
            }
        ]
    },
    plugins: [
        new CleanWebpackPlugin(), // 会默认清空以往的 output.path 文件夹
        new webpack.DefinePlugin({
            // window.ENV = 'production'
            ENV: JSON.stringify('production')
        }),
        // 抽离 css 文件
        new MiniCssExtractPlugin({
          filename: 'css/main.[contentHash:8].css'
        }),
      
        // 忽略 moment 下的 /locale 目录
        new webpack.IgnorePlugin(/./locale/, /moment//),
                                 
        // happyPack 开启 多进程打包
        new HappyPack({
          // 用唯一的标识符 id 来代表当前的 HappyPack 使用来处理一类特定的文件
          id: 'babel',
          // 如何处理 .js 文件,用法和 loader 配置中一样
          loaders: ['babel-loader?cacheDirectory']
        })
    ],
  
    optimization: {
      // 压缩 css
      minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})]
      
      // 分割代码块
      splitChunks: {
        /*
          initial:入口 chunk,对于 异步导入 的文件不处理
          async:异步 chunk,只针对 异步导入 的文件进行处理
          all:全部 chunk
        */
        chunks: 'all',
      
        // 缓存分组
        cacheGroups: {
          // 第三方模块
          vendor: {
            name: 'vendor', // chunk 名称
            priority: 1, // 权限更高,优先抽离,重要!!!
            test: /node_modules/,
            minSize: 0, // 大小限制
            minChunks: 1 // 最少复用过几次
          },
          
          // 公共的模块
          common: {
            name: 'common', // chunk 名称
            priority: 0, // 优先级
            minSize: 0, // 公共模块的大小限制
            minChunks: 2 // 公共模块最少复用过几次
          }
        }
      }
    }
})

关键代码:

const HappyPack = require('happyPack')
​
module: {
        rules: [
            // JS 的文件处理
            {
              test: /.js$/,
              // 将对 .js 文件的处理转交给 id 为 babel 的 HappyPack 实例
              use: ['happypack/loader?id=babel'],
              include: srcPath,
              // exclude: /node_module/
            }
        ]
}
​
plugins: [                      
        // happyPack 开启 多进程打包
        new HappyPack({
          // 用唯一的标识符 id 来代表当前的 HappyPack 使用来处理一类特定的文件
          id: 'babel',
          // 如何处理 .js 文件,用法和 loader 配置中一样
          loaders: ['babel-loader?cacheDirectory']
        })
]

ParallelUglifyPlugin:

- JS 单线程,开启 多进程压缩 JS 更快;- webpack 内置有 Uglify 工具压缩 JS,但是效率低下;- 原理和 happyPack 同理

webpack.prod.js:

const path = require('path')
const webpack = require('webpack')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const TerserJSPlugin = require('terser-webpack-plugin')
const OptimizeAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const HappyPack = require('happyPack')
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin')
const webpackCommonConf = require('./webpack.common.js')
const { smart } = require('webpack-merge')
const { srcPath, distPath } = require('./paths')
​
module.exports = smart(webpackCommonConf, {
    mode: 'production',
    output: {
        filename: '[name].[contentHash:8].js',  // 打包代码时,加上 hash 戳(内容变了,Hash 值就变化,否则就不变 —— 缓存)
        path: distPath,
        // publicPath: 'http://cdn.abc.com'  // 修改所有静态文件 url 的前缀(如 cdn 域名),这里暂时用不到
    },
    module: {
        rules: [
            // JS
            {
              test: /.js$/,
              // 将对 .js 文件的处理转交给 id 为 babel 的 HappyPack 实例
              use: ['happypack/loader?id=babel'],
              include: srcPath,
              // exclude: /node_module/
            }
            // 图片 - 考虑 base64 编码的情况 减少网络请求
            {
                test: /.(png|jpg|jpeg|gif)$/,
                use: {
                    loader: 'url-loader',
                    options: {
                        // 小于 5kb 的图片用 base64 格式产出
                        // 否则,依然延用 file-loader 的形式,产出 url 格式
                        limit: 5 * 1024,
​
                        // 打包到 img 目录下
                        outputPath: '/img1/',
​
                        // 设置图片的 cdn 地址(也可以统一在外面的 output 中设置,那将作用于所有静态资源)
                        // publicPath: 'http://cdn.abc.com'
                    }
                }
            },
            // 抽离 css
            {
              test: /.css$/,
              loader: [
                // 注意:这里不再用 style-loader
                MiniCssExtractPlugin.loader,
                'css-loader',
                'postcss-loader'
              ]
            },
            // 抽离 less -> css
            {
              test: /.less$/,
              loader: [
                // 注意:这里不再用 style-loader
                MiniCssExtractPlugin.loader,
                'css-loader',
                'less-loader',
                'postcss-loader'
              ]
            }
        ]
    },
    plugins: [
        new CleanWebpackPlugin(), // 会默认清空以往的 output.path 文件夹
        new webpack.DefinePlugin({
            // window.ENV = 'production'
            ENV: JSON.stringify('production')
        }),
        // 抽离 css 文件
        new MiniCssExtractPlugin({
          filename: 'css/main.[contentHash:8].css'
        }),
      
        // 忽略 moment 下的 /locale 目录
        new webpack.IgnorePlugin(/./locale/, /moment//),
                                 
        // happyPack 开启 多进程打包
        new HappyPack({
          // 用唯一的标识符 id 来代表当前的 HappyPack 使用来处理一类特定的文件
          id: 'babel',
          // 如何处理 .js 文件,用法和 loader 配置中一样
          loaders: ['babel-loader?cacheDirectory']
        }),
                                 
        // 使用 parallelUglifyPlugin 进行压缩输出的 JS 代码
        new ParallelUglifyPlugin({
          // 传递给 UglifyJS 的参数
          // 本质上还是使用 UglifyJS 压缩,只不过帮助开启了多进程
          uglifyJS: {
            output: {
              beautify: false, // 最紧凑的输出
              comments: false, // 删除所有的注释
            },
            compress: {
              // 删除所有的 'console' 语句,可以兼容 IE 浏览器
              drop_console: true,
              // 内嵌定义了但是只用到一次的变量
              collapse_vars: true,
              // 提取出出现多次但是没有定义成变量去引用的静态值
              reduce_vars: true
            }
          }
        })
    ],
  
    optimization: {
      // 压缩 css
      minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})]
      
      // 分割代码块
      splitChunks: {
        /*
          initial:入口 chunk,对于 异步导入 的文件不处理
          async:异步 chunk,只针对 异步导入 的文件进行处理
          all:全部 chunk
        */
        chunks: 'all',
      
        // 缓存分组
        cacheGroups: {
          // 第三方模块
          vendor: {
            name: 'vendor', // chunk 名称
            priority: 1, // 权限更高,优先抽离,重要!!!
            test: /node_modules/,
            minSize: 0, // 大小限制
            minChunks: 1 // 最少复用过几次
          },
          
          // 公共的模块
          common: {
            name: 'common', // chunk 名称
            priority: 0, // 优先级
            minSize: 0, // 公共模块的大小限制
            minChunks: 2 // 公共模块最少复用过几次
          }
        }
      }
    }
})

关键代码:

const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin')
​
plugins: [                        
        // 使用 parallelUglifyPlugin 进行压缩输出的 JS 代码
        new ParallelUglifyPlugin({
          // 传递给 UglifyJS 的参数
          // 本质上还是使用 UglifyJS 压缩,只不过帮助开启了多进程
          uglifyJS: {
            output: {
              beautify: false, // 最紧凑的输出
              comments: false, // 删除所有的注释
            },
            compress: {
              // 删除所有的 'console' 语句,可以兼容 IE 浏览器
              drop_console: true,
              // 内嵌定义了但是只用到一次的变量
              collapse_vars: true,
              // 提取出出现多次但是没有定义成变量去引用的静态值
              reduce_vars: true
            }
          }
        })
    ]

关于 开启多进程:

- 项目较大,打包较慢,开启多进程能提高速度;- 项目较小,打包很快,开启多进程会降低速度(进程开销);- 按需使用

自动刷新:

- 保存代码后,界面会自动刷新一遍;- 整个网页全部刷新,速度较慢;- 刷新后,状态会丢失(内存全部清空)

一般情况下 不会使用:因为一般在开发中需要自动刷新调试代码,但是 webpack-dev-server 已经默认开启了

module.export = {
  watch: true, // 开启监听,默认为 false
  // 注意:开启监听之后,webpack-dev-server 会自动开启刷新浏览器
  
  // 监听配置
  watchOptions: {
    ignored: /node_modules/,
    // 监听到变化发生后等待 300ms 再去执行动作,防止文件更新太快导致重新编译频率太高
    aggregateTimeout: 300, // 默认为 300ms
    // 判断文件是否发生变化
    // 通过不停地询问系统指定文件有没有变化实现
    poll: 1000 // 默认每间隔 1000ms 询问一次
  }
}

热更新:

- 新代码生效,网页不刷新,状态不丢失;- 按需使用,有成本(需要自定义 热更新 的监听范围)

webpack.dev.js:

const path = require('path')
const webpack = require('webpack')
const webpackCommonConf = require('./webpack.common.js')
const { smart } = require('webpack-merge')
const { srcPath, distPath } = require('./paths')
const HotModuleReplacementPlugin = require('webpack/lib/HotModuleReplacementPlugin');
​
module.exports = smart(webpackCommonConf, {
    mode: 'development',
    entry: {
        // index: path.join(srcPath, 'index.js'),
        index: [
            'webpack-dev-server/client?http://localhost:8080/',
            'webpack/hot/dev-server',
            path.join(srcPath, 'index.js')
        ],
        other: path.join(srcPath, 'other.js')
    },
    module: {
        rules: [
            {
                test: /.js$/,
                loader: ['babel-loader?cacheDirectory'],
                include: srcPath,
                // exclude: /node_modules/
            },
            // 直接引入图片 url
            {
                test: /.(png|jpg|jpeg|gif)$/,
                use: 'file-loader'
            },
            // {
            //     test: /.css$/,
            //     // loader 的执行顺序是:从后往前
            //     loader: ['style-loader', 'css-loader']
            // },
            {
                test: /.css$/,
                // loader 的执行顺序是:从后往前
                loader: ['style-loader', 'css-loader', 'postcss-loader'] // 加了 postcss
            },
            {
                test: /.less$/,
                // 增加 'less-loader' ,注意顺序
                loader: ['style-loader', 'css-loader', 'less-loader']
            }
        ]
    },
    plugins: [
        new webpack.DefinePlugin({
            // window.ENV = 'production'
            ENV: JSON.stringify('development')
        }),
        new HotModuleReplacementPlugin()
    ],
    devServer: {
        port: 8080,
        progress: true,  // 显示打包的进度条
        contentBase: distPath,  // 根目录
        open: true,  // 自动打开浏览器
        compress: true,  // 启动 gzip 压缩
​
        hot: true,
​
        // 设置代理
        proxy: {
            // 将本地 /api/xxx 代理到 localhost:3000/api/xxx
            '/api': 'http://localhost:3000',
​
            // 将本地 /api2/xxx 代理到 localhost:3000/xxx
            '/api2': {
                target: 'http://localhost:3000',
                pathRewrite: {
                    '/api2': ''
                }
            }
        }
    },
    // watch: true, // 开启监听,默认为 false
    // watchOptions: {
    //     ignored: /node_modules/, // 忽略哪些
    //     // 监听到变化发生后会等300ms再去执行动作,防止文件更新太快导致重新编译频率太高
    //     // 默认为 300ms
    //     aggregateTimeout: 300,
    //     // 判断文件是否发生变化是通过不停的去询问系统指定文件有没有变化实现的
    //     // 默认每隔1000毫秒询问一次
    //     poll: 1000
    // }
})

关键代码:

const HotModuleReplacementPlugin = require('webpack/lib/HotModuleReplacementPlugin');
​
module.exports = smart(webpackCommonConf, {
    mode: 'development',
    entry: {
        // index: path.join(srcPath, 'index.js'),
        index: [
            'webpack-dev-server/client?http://localhost:8080/',
            'webpack/hot/dev-server',
            path.join(srcPath, 'index.js')
        ],
        other: path.join(srcPath, 'other.js')
    },
    plugins: [
        new webpack.DefinePlugin({
            // window.ENV = 'production'
            ENV: JSON.stringify('development')
        }),
        new HotModuleReplacementPlugin()
    ],
    devServer: {
        port: 8080,
        progress: true,  // 显示打包的进度条
        contentBase: distPath,  // 根目录
        open: true,  // 自动打开浏览器
        compress: true,  // 启动 gzip 压缩
​
        hot: true,
})

index.js:

// 引入 css
import './style/style1.css'
import './style/style2.less'import { sum } from './math'const sumRes = sum(10, 20)
console.log('sumRes', sumRes)
​
// 增加,开启 热更新 之后的代码逻辑
if (module.hot) {
  module.hot.accept(['./math'], () => {
    const sumRes = sum(10, 30)
    console.log('sumRes in hot', sumRes)
  })
}

关键代码:

// 增加,开启 热更新 之后的代码逻辑
if (module.hot) {
  module.hot.accept(['./math'], () => {
    const sumRes = sum(10, 30) // 调用的是这里
    console.log('sumRes in hot', sumRes)
  })
}

注意:在监听范围内修改才会触发 热更新

math.js:

// 在该文件中修改,才会触发 热更新
export const sum = (a, b) => {
  return a + b + 5 // 调用的是 被监听 的回调
}

hard-source-webpack-plugin:

- 动态链接库插件,为模块提供了 中间缓存 的步骤;- 第一次构建将花费正常时间,第二次构建速度显著加快 80%~90%

背景:

- 前端框架如 Vue React,体积大,构建慢;但是较稳定,不常升级版本;- 同一个版本只构建一次即可,不用每次都重新构建;

安装

npm install hard-source-webpack-plugin -D

使用:

const HardSourceWebpackPlugin = require('hard-source-webpack-plugin')
​
module.export = {
  // ...
  plugins: [
    new HardSourceWebpackPlugin()
  ]
}

总结:

可用于 生产环境 的 优化构建速度:

- 优化 babel-loader;- IgnorePlugin;- happyPack;- ParallelUglifyPlugin;

不可用于 生产环境 的 优化构建速度:

- 自动刷新;- 热更新;- HardSourceWebpackPlugin

优化产出代码:

优点:

- 代码体积更小;- 合理分包(重复代码),不重复加载;- 速度更快,内存使用更少

小图片 base64 编码:

module: {
  rules: [
    // ...
    {
      test: /.(png|jpg|jpeg|gif)$/,
      use: {
        loader: 'url-loader',
        options: {
          // 小于 5KB 的图片用 base64 格式产出
          // 否则,依然沿用 file-loader的形式,产出 URL 格式
          limit: 5 * 1024
          
          // 打包到 img1 目录下
          outputPath: '/img1/'
          
          // 设置图片的 CDN 地址(也可以统一在外面的 output 中设置)
          // publicPath: 'http://cdn.abc.com'
        }
      }
    }
  ]
}

bundle + hash:

module.exports = smart(webpackCommonConf, {
  // ...
  output: {
    filename: '[name].[contentHash:8].js'
  }
})

懒加载

提取 公共代码 和 第三方代码

IgnorePlugin

使用 CDN 加速:

module.exports = smart(webpackCommonConf, {
  // ...
  output: {
    // ...
    path: distPath,
    // 修改所有静态文件 URL 的前缀
    publicPath: 'http://cdn.abc.com'
  }
})

步骤分为两步:

- 在 webpack.prod.js 中,配置 CDN 的公共 path,并运行打包;- 将打包的结果 dist 目录下的 JS 文件和 CSS 文件以及 图片 文件,上传到 CDN上

使用 production:

mode: 'production'

优点:

- 自动开启代码压缩;- Vue React 等会自动删除调试代码(如开发环境的 warning);- 自动启动 Tree-Shaking

Tree-Shaking:

- 将无用的代码删除- 注意:
  只有用 ES6 Module 才能让 Tree-Shaking 生效
  CommonJS 就不行

Index.js:

import { sum } from './math'const sumRes = sum(10, 20)
console.log('sumRes', sumRes)

math.js:

export const sum = (a, b) => {
  return a + b
}
​
// 由于 index.js 中只使用了 sum
// 因此,以下代码被删除(Tree-Shaking)
export const mult = (a, b) => {
  return a * b
}

补充:

ES6 Module 和 CommonJS 的区别:

- ES6 Module 静态引入,编译时引入(即:不能在条件中引用);- CommonJS 动态引入,执行时引入;- 因此,只有 ES6 Module 才能静态分析,实现 Tree-Shaking

Scope Hosting:

- 普通打包会将每个文件都生成一个函数,而每个函数都会产生一个作用域,这对代码的执行,以及内存的消耗很不友好- Scope Hosting 能将多个函数合并成一个函数- 代码越复杂,优势更大:
  代码体积更小
  创建函数作用域更少
  代码可读性更好

普通打包:

hello.js:

export default 'Hello AHu'

main.js:

import str from './hello.js'
console.log(str)

打包结果:

// 默认打包结果
[
  (function (module, __webpack_exports__, __webpack_require) {
    var __WEBPACK_IMPORTED_MODULE_0__util_js__=__webpack_require__(1);
    console.log(__WEBPACK_IMPORTED_MODULE_0__util_js__["a"])
  }),
  (function (module, __webpack_exports__, __webpack_require__) {
    __webpack_exports__["a"] = ('Hello AHu')
  })
]

使用 Scope Hosting 的打包结果:

// 开启 Scope Hosting
[
  (function (module, __webpack_exports__, __webpack_require__) {
    var hello = ('Hello AHu')
    console.log(hello)
  })
]

使用配置:

使用条件:
- 必须是能通过 ES6 模块化的方式组织代码
const ModuleConcatenationPlugin = require('webpack/lib/optimize/ModuleConcatenationPlugin')
​
module.exports = {
  resolve: {
    // 针对 NPM 中的第三方模块优先采用 jsnext:main 中指向的 ES6 模块化语法的文件
    mainFields: ['jsnext:main', 'browser', 'main']
  },
  plugins: [
    // 开启 Scope Hosting
    new ModuleConcatenationPlugin()
  ]
}



babel:

环境搭建 & 基本配置

环境搭建

package.json:

{
  "name": "09-babel-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo "Error: no test specified" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/cli": "^7.7.5",
    "@babel/core": "^7.7.5",
    "@babel/loader": ""
    "@babel/plugin-transform-runtime": "^7.7.5",
    "@babel/preset-env": "^7.7.5"
  },
  "dependencies": {
    "@babel/polyfill": "^7.7.0",
    "@babel/runtime": "^7.7.5"
  }
}

配置 .babelIrc:

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

命令行 启动 babel 编译文件:

npx babel src/index.js

.babelIrc 配置

{
  "presets": [ // 预设(可以作为 babel 插件的组合)
    [
      "@babel/preset-env" // 一堆 plugin 的集合
    ]
  ],
    "plugins": []
}

presets 和 plugins

- presets:预设(可以作为 babel 插件的组合)
  @babel/preset-env
  @babel/preset-flow
  @babel/preset-react
  @babel/preset-typescript

babel-polyfill

npm install @babel-polyfill

定义:

- polyfill 是 根据浏览器的情况,做一个补丁(兼容);- babel-polyfill 是 core-js 和 regenerator 的集合:
- core-js:
  定义:polyfill 的集合(ES6语法)
  缺点:对 ES6 generator 函数(处理异步,后来被 async/await 代替)不支持
- regenerator:
  定义:支持 generator 的语法
  
- babel 7.4 之后,弃用 babel-polyfill
  推荐直接使用 core-js 和 regenerator

按需引入 babel-polyfill:

index.js:

import '@babel/polyfill'const sum = (a, b) => a + b
​
// 新的 API,但 语法 符合 ES5 的规范
Promise.resolve(100).then(data => data)
// 新的 API,但 语法 符合 ES5 的规范
[10, 20, 30].includes(20)
- babel 的本质:
  只关心语法,
  并不关心 API;
  也不处理 模块化(模块化 由 webpack 处理)

为什么需要按需引入 babel-polyfill:

- 文件较大(集成了 core-js 和 regenerator)
  只需要一部分功能,无需全部引入
  配置 按需引入 即可

更改 .babelIrc 配置文件:

{
  "presets": [
    "@babel/preset-env",
    {
      // 按需引入
      "useBuiltIns": "usage" // 按需引入
      "corejs": 3 // 版本
    }
  ],
    "plugins": []
}

更改 index.js:

const sum = (a, b) => a + b// 新的 API,但 语法 符合 ES5 的规范
Promise.resolve(100).then(data => data)
// 新的 API,但 语法 符合 ES5 的规范
[10, 20, 30].includes(20)

babel-runtime

babel-polyfill 的问题:

- 会 污染全局环境:
  - 如果做一个独立的 web 系统,则无碍;
  - 如果做一个第三方的 lib 库,则会有问题
window.Promise = function() {}
​
Array.prototype.includes = function () {}

解决方案:使用 babel-runtime

npm install @babel/runtime
​
npm install @babel/plugin-transform-runtime -D

在 .babelIrc 中配置:

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "useBuiltIns": "usage",
        "corejs": 3
      }
    ]
  ],
    "plugins": [
      [
        "@babel/plugin-transform-runtime",
        {
          "absoluteRuntime": false,
          "corejs": 3,
          "helpers": true,
          "regenerator": true,
          "useESModules": false
        }
      ]
    ]
}