(十)webpack 和 babel

99 阅读12分钟

@[TOC](webpack 和 babel)

webpack

webpack已是前端打包够贱的不二选择 每日必用,面试必考 成熟的工具,重点在于配置和使用,原理并不高优

面试题

  • 前端代码为何要进行构建和打包 代码层面 体积更小(Tree-Shaking、压缩、合并) 编译高级语言或语法(TS、ES6+、模块化、scss) 兼容性和错误检查(Polyfill、postcss、eslint) 前端工程化、前端流程、团队效率层面 统一、高效的开发环境 统一的构建流程和产出标准 集成公司构建规范(提测、上线等)

  • module chunk bundle分别什么意思,有何区别?

  • loader和plugin的区别 loader模块转换器,如less > css plugin扩展插件,如HtmlWebpackPlugin

  • 常见loader和plugin有哪些 www.webpackjs.com/loaders/ www.webpackjs.com/plugins/ 把此前示例中的loader和plugin答出来即可

  • webpack如何实现懒加载 import() 结合Vue React异步组件 结合Vue-router React-router异步加载路由

  • webpack常见性能优化

  • babel-runtime和babel-polyfill的区别 babel-polyfill会污染全局 babel-runtime不会污染全局 产出第三方lib要用babel-runtime

  • babel和webpack的区别 babel - JS新语法编译工具,不关心模块化 webpack - 打包构建工具,是多个loader plugin的集合

  • 如何产出一个lib 参考webpack.dll.js output.library 在这里插入图片描述

  • 为何Proxy不能被polyfill 如Class可以用function模拟 如Promise可以用callback来模拟 但Proxy的功能用Object.defineProperty无法模拟

基本配置

拆分配置和merge

const webpackCommonConf = require('./webpack.common.js')
const { smart } = require('webpack-merge')

module.exports = smart(webpackCommonConf, {
})

启动本地服务

    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': ''
                }
            }
        }
    }

处理ES6

{
      test: /\.js$/,
      loader: ['babel-loader'],
      include: srcPath,
      exclude: /node_modules/
},
//.babelrc
{
    "presets": ["@babel/preset-env"],
    "plugins": []
}

处理样式

  // {
  //     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']
  }
// postcss-loader是为了处理兼容性,还要配置postcss.config.js才会生效
//postcss.config.js
module.exports = {
    plugins: [require('autoprefixer')]
}

处理图片

 // dev直接引入图片 url
 {
     test: /\.(png|jpg|jpeg|gif)$/,
     use: 'file-loader'
 }
 
 //prod情况小于 5kb 的图片用 base64 格式产出,其他产出 url 格式放在img目录下
 // 图片 - 考虑 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'
         }
     }
 },

模块化

总结

//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"
  }
}

//paths.js
/**
 * @description 常用文件夹路径
 * @author 双越
 */

const path = require('path')

const srcPath = path.join(__dirname, '..', 'src')
const distPath = path.join(__dirname, '..', 'dist')

module.exports = {
    srcPath,
    distPath
}
//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: [
            {
                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'
        })
    ]
}
//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')
        })
    ],
    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 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 戳
        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')
        })
    ]
})

//.babelrc
{
    "presets": ["@babel/preset-env"],
    "plugins": []
}
//postcss.config.js
module.exports = {
    plugins: [require('autoprefixer')]
}

高级配置

基本配置只能做demo,不能做线上项目 面试考察基本配置,只是为了快速判断你是否用过webpack 以下高级配置,也是通过面试的必要条件

多入口

//webpack.common.js
    entry: {
        index: path.join(srcPath, 'index.js'),
        other: path.join(srcPath, 'other.js')
    },
       // 多入口 - 生成 index.html
   new HtmlWebpackPlugin({
       template: path.join(srcPath, 'index.html'),
       filename: 'index.html',
       // chunks 表示该页面要引用哪些 chunk (即上面的 index 和 other),默认全部引用
       chunks: ['index']  // 只引用 index.js
   }),
   // 多入口 - 生成 other.html
   new HtmlWebpackPlugin({
       template: path.join(srcPath, 'other.html'),
       filename: 'other.html',
       chunks: ['other']  // 只引用 other.js
   })
//webpack.prod.js
output: {
        // filename: 'bundle.[contentHash:8].js',  // 打包代码时,加上 hash 戳
        filename: '[name].[contentHash:8].js', // name 即多入口时 entry 的 key
        path: distPath,
        // publicPath: 'http://cdn.abc.com'  // 修改所有静态文件 url 的前缀(如 cdn 域名),这里暂时用不到
    },

抽离css文件

//webpack.prod.js
const path = require('path')
const webpack = require('webpack')
const { smart } = require('webpack-merge')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const TerserJSPlugin = require('terser-webpack-plugin')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const webpackCommonConf = require('./webpack.common.js')
const { srcPath, distPath } = require('./paths')

module.exports = smart(webpackCommonConf, {
    mode: 'production',
    output: {
        // filename: 'bundle.[contentHash:8].js',  // 打包代码时,加上 hash 戳
        filename: '[name].[contentHash:8].js', // name 即多入口时 entry 的 key
        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: [
                    MiniCssExtractPlugin.loader,  // 注意,这里不再用 style-loader
                    'css-loader',
                    'postcss-loader'
                ]
            },
            // 抽离 less --> css
            {
                test: /\.less$/,
                loader: [
                    MiniCssExtractPlugin.loader,  // 注意,这里不再用 style-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({})],
    }
})

//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.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: [
            {
                test: /\.js$/,
                loader: ['babel-loader'],
                include: srcPath,
                exclude: /node_modules/
            }
            // css 处理
        ]
    },
    plugins: [
        // new HtmlWebpackPlugin({
        //     template: path.join(srcPath, 'index.html'),
        //     filename: 'index.html'
        // })

        // 多入口 - 生成 index.html
        new HtmlWebpackPlugin({
            template: path.join(srcPath, 'index.html'),
            filename: 'index.html',
            // chunks 表示该页面要引用哪些 chunk (即上面的 index 和 other),默认全部引用
            chunks: ['index']  // 只引用 index.js
        }),
        // 多入口 - 生成 other.html
        new HtmlWebpackPlugin({
            template: path.join(srcPath, 'other.html'),
            filename: 'other.html',
            chunks: ['other']  // 只引用 other.js
        })
    ]
}

抽离公共代码

//webpack.prod.js
const path = require('path')
const webpack = require('webpack')
const { smart } = require('webpack-merge')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const TerserJSPlugin = require('terser-webpack-plugin')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const webpackCommonConf = require('./webpack.common.js')
const { srcPath, distPath } = require('./paths')

module.exports = smart(webpackCommonConf, {
    mode: 'production',
    output: {
        // filename: 'bundle.[contentHash:8].js',  // 打包代码时,加上 hash 戳
        filename: '[name].[contentHash:8].js', // name 即多入口时 entry 的 key
        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: [
                    MiniCssExtractPlugin.loader,  // 注意,这里不再用 style-loader
                    'css-loader',
                    'postcss-loader'
                ]
            },
            // 抽离 less
            {
                test: /\.less$/,
                loader: [
                    MiniCssExtractPlugin.loader,  // 注意,这里不再用 style-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: {
            chunks: 'all',
            /**
             * initial 入口 chunk,对于异步导入的文件不处理
                async 异步 chunk,只对异步导入的文件处理
                all 全部 chunk
             */

            // 缓存分组
            cacheGroups: {
                // 第三方模块
                vendor: {
                    name: 'vendor', // chunk 名称
                    priority: 1, // 权限更高,优先抽离,重要!!!
                    test: /node_modules/,
                    minSize: 0,  // 大小限制,多少kb才打包,为了防止比较小的包打包出来反而浪费资源
                    minChunks: 1  // 最少复用过几次 引用了一次就单独抽离出
                },

                // 公共的模块
                common: {
                    name: 'common', // chunk 名称
                    priority: 0, // 优先级
                    minSize: 0,  // 公共模块的大小限制,多少kb才打包,为了防止比较小的包打包出来反而浪费资源
                    minChunks: 2  // 公共模块最少复用过几次 引用了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: [
            {
                test: /\.js$/,
                loader: ['babel-loader'],
                include: srcPath,
                exclude: /node_modules/
            }
        ]
    },
    plugins: [
        // new HtmlWebpackPlugin({
        //     template: path.join(srcPath, 'index.html'),
        //     filename: 'index.html'
        // })

        // 多入口 - 生成 index.html
        new HtmlWebpackPlugin({
            template: path.join(srcPath, 'index.html'),
            filename: 'index.html',
            // chunks 表示该页面要引用哪些 chunk (即上面的 index 和 other),默认全部引用
            chunks: ['index', 'vendor', 'common']  // 要考虑代码分割
        }),
        // 多入口 - 生成 other.html
        new HtmlWebpackPlugin({
            template: path.join(srcPath, 'other.html'),
            filename: 'other.html',
            chunks: ['other', 'common']  // 考虑代码分割
        })
    ]
}

懒加载

//引入动态数据 - 懒加载
setTimeout(() => {
	import('./dynamic-data.js').then(res=>{
		console.log(res.default.message) //注意这里的default
	})
},1500)
 

处理JSX

//.babelrc 
{
    "presets": ["@babel/preset-react"],
    "plugins": []
}
//webpack配置
{
      test: /\.js$/,
      loader: ['babel-loader'],
      include: srcPath,
      exclude: /node_modules/
  },

处理vue

 {
     test: /\.vue$/,
     loader: ['vue-loader'],
     include: srcPath
 },

module chunk bundle的区别

module - 各个源码文件,webpack中一切皆模块,所有引入都是模块,js、css、img都是模块 chunk - 多模块合并成的,如entry import() splitChunk bundle - 最终的输出文件,一般来说一个chunk对应一个bundle 在这里插入图片描述

module chunk bundle分别对应图中的左中右

webpack性能优化

大厂必考 & 社区热议话题 优化打包构建速度 - 开发体验和效率 优化产出代码 - 产品性能

构建速度

优化babel-loader

在这里插入图片描述 开启缓存,es6代码没改的不会重新编译

IgnorePlugin避免引入无用的模块

import moment from 'moment' 默认会引入所有语言JS代码,代码过大 如何只引入中文?

//webpack.prod.js
  // 忽略 moment 下的 /locale 目录
 new webpack.IgnorePlugin(/\.\/locale/, /moment/),
 //index.js
 import moment from 'moment'
import 'moment/locale/zh-cn' //手动引入中文语言
moment.locale('zh-cn')//设置语言为中文
console.log('locale',moment.locale)
console.log('data',moment().format('ll')) //2020年xx月xx日
 

noParse避免重复打包

min,js基本上已经采用模块化处理过的 在这里插入图片描述

IgnorePlugin vs noParse

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

happyPack多进程打包

JS单线程,开启多进程打包 提高构建速度(特别是多核CPU)

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

ParallelUglifyPlugin多进程压缩JS

webpack内置Uglify工具压缩JS JS单线程,开启多进程压缩更快 和happyPack同理

const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin')

// 使用 ParallelUglifyPlugin 并行压缩输出的 JS 代码
new ParallelUglifyPlugin({
    // 传递给 UglifyJS 的参数
    // (还是使用 UglifyJS 压缩,只不过帮助开启了多进程)
    uglifyJS: {
        output: {
            beautify: false, // 最紧凑的输出
            comments: false, // 删除所有的注释
        },
        compress: {
            // 删除所有的 `console` 语句,可以兼容ie浏览器
            drop_console: true,
            // 内嵌定义了但是只用到一次的变量
            collapse_vars: true,
            // 提取出出现多次但是没有定义成变量去引用的静态值
            reduce_vars: true,
        }
    }
})
关于开启多进程

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

自动刷新

    // watch: true, // 开启监听,默认为 false
    // watchOptions: {
    //     ignored: /node_modules/, // 忽略哪些
    //     // 监听到变化发生后会等300ms再去执行动作,防止文件更新太快导致重新编译频率太高
    //     // 默认为 300ms
    //     aggregateTimeout: 300,
    //     // 判断文件是否发生变化是通过不停的去询问系统指定文件有没有变化实现的
    //     // 默认每隔1000毫秒询问一次
    //     poll: 1000
    // }

在这里插入图片描述

//devServer会默认开启自动刷新
    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': ''
                }
            }
        }
    },

热更新

自动刷新:整个网页全部刷新,速度较慢 自动刷新:整个网页全部刷新,状态会丢失 热更新:新代码生效,网页不刷新,状态不丢失

//webpack.dev.js
const HotModuleReplacementPlugin = require('webpack/lib/HotModuleReplacementPlugin');
    entry: {
        // index: path.join(srcPath, 'index.js'),
        index: [
            'webpack-dev-server/client?http://localhost:8080/',
            'webpack/hot/dev-server',
            path.join(srcPath, 'index.js')
        ]
    },
    plugins: [
        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': ''
                }
            }
        }
    },
//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)
     })
 }

DllPlugin动态链接库插件

针对一些比较大的库,第三方插件,没有必要每次打包时都打包一遍,可以事先打包好以后作为dll.然后引用它

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

webpack已内置DllPlugin支持 DllPlugin - 打包出dll文件 DllReferencePlugin - 使用dll文件

//package.json
{
  "name": "08-webpack-dll-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "webpack-dev-server --config build/webpack.dev.js",
    "dll": "webpack --config build/webpack.dll.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "react": "^16.12.0",
    "react-dom": "^16.12.0"
  },
  "devDependencies": {
    "@babel/core": "^7.7.5",
    "@babel/preset-env": "^7.7.5",
    "@babel/preset-react": "^7.7.4",
    "babel-loader": "^8.0.6",
    "html-webpack-plugin": "^3.2.0",
    "webpack": "^4.41.2",
    "webpack-cli": "^3.3.10",
    "webpack-dev-server": "^3.9.0",
    "webpack-merge": "^4.2.2"
  }
}
//webpack.dll.js
const path = require('path')
const DllPlugin = require('webpack/lib/DllPlugin')
const { srcPath, distPath } = require('./paths')

module.exports = {
  mode: 'development',
  // JS 执行入口文件
  entry: {
    // 把 React 相关模块的放到一个单独的动态链接库
    react: ['react', 'react-dom']
  },
  output: {
    // 输出的动态链接库的文件名称,[name] 代表当前动态链接库的名称,
    // 也就是 entry 中配置的 react 和 polyfill
    filename: '[name].dll.js',
    // 输出的文件都放到 dist 目录下
    path: distPath,
    // 存放动态链接库的全局变量名称,例如对应 react 来说就是 _dll_react
    // 之所以在前面加上 _dll_ 是为了防止全局变量冲突
    library: '_dll_[name]',
  },
  plugins: [
    // 接入 DllPlugin
    new DllPlugin({
      // 动态链接库的全局变量名称,需要和 output.library 中保持一致
      // 该字段的值也就是输出的 manifest.json 文件 中 name 字段的值
      // 例如 react.manifest.json 中就有 "name": "_dll_react"
      name: '_dll_[name]',
      // 描述动态链接库的 manifest.json 文件输出时的文件名称
      path: path.join(distPath, '[name].manifest.json'),
    }),
  ],
}
npm run dll后dist文件夹下会生成react.dll.js和react.manifest.json
//index.html
 <script src="./react.dll.js"></script>
//webpack.dev.js

// 第一,引入 DllReferencePlugin
const DllReferencePlugin = require('webpack/lib/DllReferencePlugin');

 module: {
        rules: [
            {
                test: /\.js$/,
                loader: ['babel-loader'],
                include: srcPath,
                exclude: /node_modules/ // 第二,不要再转换 node_modules 的代码
            },
        ]
    },
plugins: [
    // 第三,告诉 Webpack 使用了哪些动态链接库
    new DllReferencePlugin({
        // 描述 react 动态链接库的文件内容
        manifest: require(path.join(distPath, 'react.manifest.json')),
    }),
],

总结

webpack优化构建速度(可用于生产环境)

优化babel-loader IgnorePlugin noParse happyPack ParallelUglifyPlugin

webpack优化构建速度(不用于生产环境)

自动刷新 热更新 DllPlugin

优化产出代码

体积更小 合理分包,不重复加载 速度更快、内存使用更小

小图片base64编码

bundle加hash

懒加载

提取公共代码

IgnorePlugin使打出体积更小

使用CDN加速

//webpack.prod.js中配置cdn地址
 output: {
        publicPath: 'http://cdn.abc.com'  // 修改所有静态文件 url 的前缀(如 cdn 域名),这里暂时用不到
    },
  {
    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'
        }
    }
}
把静态文件上传至CDN

使用production

  • 自动开启代码压缩
  • Vue React等会自动删掉调试代码(如开发环境的warning)
  • 启动Tree-Shaking,没有用到的方法不会加载,把没有用的的摇掉 // ES6 Module 才能让 tree-shaking 生效 // commonjs 就不行
ES6 Module和commonjs的区别

ES6 Module静态引入,编译时引入 Commonjs动态引入,执行时引入 只有ES6 Module才能静态分析,实现Rree-Shaking 在这里插入图片描述

Scope Hosting

在这里插入图片描述 在这里插入图片描述 一个函数就有一个作用域,很多文件就会产生很多函数,很多作用域 在这里插入图片描述 开启Scope Hosting之后,很多文件都放在一个函数里,作用域数量更少,内存占用更少,也不会频繁的去跨作用域调用,JS代码也会执行得快一些

代码体积更小 创建函数作用域更少 代码可读性更好 在这里插入图片描述

babel

前端开发环境必备工具 同webpack,需要了解基本的配置和使用 面试考察概率不高,但要求必会

环境搭建 & 基本配置

  • 环境搭建
  • .babelrc配置
  • presets和plugins
//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/plugin-transform-runtime": "^7.7.5",
    "@babel/preset-env": "^7.7.5"
  },
  "dependencies": {
    "@babel/polyfill": "^7.7.0",
    "@babel/runtime": "^7.7.5"
  }
}

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

preset-env是一堆常用plugins的集合

//index.js
const sum = (a,b) => a + b

npx babel src/index.js 在这里插入图片描述

babel-polyfill

什么是Polyfill

浏览器补丁、兼容

core-js和regenerator

标准库,集成了ES6、ES7新语法的Polyfill 在这里插入图片描述 core-js不能处理generator函数,要用regenerator库

babel-polyfill即两者的集合

Babel7.4之后弃用babel-polyfill 推荐直接使用core-js和regenerator 但不影响面试会考察它

babel-polyfill如何按需引入

在这里插入图片描述

babel只关心语法,即便API不支持,只要语法符合规范,不管API,也不管模块化 在这里插入图片描述 文件较大 只有一部分功能,无需全部引入 配置按需引入

//babelrc
{
    "presets": [
        [
            "@babel/preset-env",
            {
                "useBuiltIns": "usage",
                "corejs": 3
            }
        ]
    ],
    "plugins": [
    ]
}
//index.js
const sum = (a, b) => a + b

// 新的 API
Promise.resolve(100).then(data => data);

// 新的 API
[10, 20, 30].includes(20)

// 语法,符合 ES5 语法规范
// 不处理模块化(webpack)

在这里插入图片描述 没有使用babel-polyfill,直接引入core-js,babel-polyfill可以不用下载

babel-runtime

babel-polyfill的问题

  • 会污染全局环境
  • 如果做一个独立的web系统,则无碍
  • 如果做一个第三方lib,则会有问题
 // core.js要这样处理,会污染全局环境
// window.Promise1 = function() {}
// Array.prototype.includes1 = function () {}

// 使用方可能这样用,会出现问题
// window.Promise = 'abc'
// Array.prototype.includes = 100

babel-runtime处理这个问题

devDependencies安装 "@babel/plugin-transform-runtime": "^7.7.5", dependencies安装 "@babel/runtime": "^7.7.5"

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

在这里插入图片描述 配置后编译出来的promise和includes前面有_,就不会有污染全局作用域问题