前端框架面試之 webpack、babel 和 webpack 的面試真題

一、webpack 和 babel

  1. 對於 webpack,分爲基本配置、高級配置、優化打包效率、優化產出代碼、構建流程概述和 babel
  2. 對於 webpack 的基本配置,拆分配置和 merge,啓動本地服務,處理 ES6,處理樣式,處理圖片和模塊化,代碼如下所示:
  • 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')
        })
    ]
})

  • path.js

const path = require('path')

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

module.exports = {
    srcPath,
    distPath
}

  1. 對於 webpack 的高級配置中的多入口,代碼如下所示:
  • 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/
            },
            // {
            //     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'
        // })

        // 多入口 - 生成 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.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 = '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 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 戳
        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'
                    }
                }
            },
        ]
    },
    plugins: [
        new CleanWebpackPlugin(), // 會默認清空 output.path 文件夾
        new webpack.DefinePlugin({
            // window.ENV = 'production'
            ENV: JSON.stringify('production')
        })
    ]
})

  • paths.js

const path = require('path')

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

module.exports = {
    srcPath,
    distPath
}

  1. 對於 webpack 的高級配置中的抽離 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: [
            {
                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.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 { 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({})],
    }
})

  • paths.js
const path = require('path')

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

module.exports = {
    srcPath,
    distPath
}

  1. 對於 webpack 的高級配置中的抽離公共代碼和第三方代碼,代碼如下所示:
  • 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']  // 考慮代碼分割
        })
    ]
}

  • 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 { 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,  // 大小限制
                    minChunks: 1  // 最少複用過幾次
                },

                // 公共的模塊
                common: {
                    name: 'common', // chunk 名稱
                    priority: 0, // 優先級
                    minSize: 0,  // 公共模塊的大小限制
                    minChunks: 2  // 公共模塊最少複用過幾次
                }
            }
        }
    }
})

  • paths.js
const path = require('path')

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

module.exports = {
    srcPath,
    distPath
}

  1. 對於 webpack 的高級配置中,除了上面所提到的多入口、抽離 CSS 文件和 抽離公共代碼,還有異步加載 JS、懶加載、處理 JSX 和處理 Vue,核心配置也是匹配對應的後綴文件,以及 loader,代碼如下所示:
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'
        })
    ]
}

  1. module、chunk、bundle 的區別是什麼,答案如下所示:
  • module 是各個源碼文件,webpack 中一切皆模塊
  • chunk 是多模塊合併成的,如 entery、import()、splitChunk 等等
  • bundle 是最終輸出的文件
  1. 對於 webpack 性能優化,優化構建速度,如下所示:
  • 優化 babel-loader。開啓緩存,明確範圍,排除範圍,includeexclude 兩者選一個即可
  • IgnorePlugin
  • noParse
  • happyPack 多進程打包。JS 單線程,開啓多進程打包,提高構建速度,特別是多核 CPU
  • ParallelUglifyPlugin 多進程壓縮 JSwebpack 內置 Uglify 工具壓縮 JSJS 單線程,開啓多進程壓縮更快,和 happyPack 原理相同
  • 自動刷新。開啓監聽,監聽配置,webpack-dev-server 會自動開啓刷新瀏覽器
  • 熱更新。自動刷新會使得整個網頁全部刷新,速度較慢,狀態會丟失。但是熱更新,新代碼生效,網頁不刷新,狀態不丟失
  • DllPlugin 動態鏈接庫插件。前端框架如 React、Vue,體積大,構建慢,較穩定,不常升級版本,同一版本只構建一次即可,不用每次都重新構建。webpack 已內置 DllPlugin 支持,DllPlugin 打包出 dll 文件,DllReferencePlugin 使用 dll 文件
  1. 對於開啓多進程,項目較大,打包較慢,開啓多進程能提高速度,項目較小,打包很快,開啓多進程會減低速度,進行開銷,按需使用。

  2. 對於 webpack 優化構建速度,可用於生產環境的,優化 babel-loader、 IgnorePlugin、noParse、happyPack、 ParallelUglifyPlugin。對於 webpack 優化構建速度,不用於生產環境的,自動刷新、熱更新、DllPlugin

  3. 對於 webpack 性能優化,產出代碼,如下所示:

  • 體積更小
  • 合理分包,不重複加載
  • 速度更快,內存使用更少
  • 小圖片 base64 編碼
  • bundlehash
  • 懶加載
  • 提取公共代碼
  • IgnorePlugin
  • 使用 CDN 加速
  • 使用 production
  • Scope Hosting
  1. 對於使用 production,如下所示:
  • 自動開啓代碼壓縮
  • Vue、React 等會自動刪掉調試代碼,如開發環境的 warning
  • 啓動 Tree-Shaking
  1. 對於 ES6 ModuleCommonjs 區別,如下所示:
  • ES6 Module 靜態引入,編譯時引入
  • Commonjs 動態引入,執行時引入
  • 只有 ES6 Module 才能靜態分析,實現 Tree-Shaking
  1. 對於 Scope Hosting,如下所示:
  • 代碼體積更小
  • 創建函數作用域更少
  • 代碼可讀性更好
  • 針對 Npm 中的第三方模塊優先採用 jsnext:main 中指向的 ES6 模塊化語法的文件,開啓 Scope Hosting
  1. 對於 webpack 性能優化,優化構建速度,代碼如下所示:
  • 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: [
            // babel-loader
        ]
    },
    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', 'vendor', 'common']  // 考慮代碼分割
        })
    ]
}

  • 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
    // }
})

  • 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 HappyPack = require('happypack')
const ParallelUglifyPlugin = require('webpack-parallel-uglify-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: [
            // js
            {
                test: /\.js$/,
                // 把對 .js 文件的處理轉交給 id 爲 babel 的 HappyPack 實例
                use: ['happypack/loader?id=babel'],
                include: srcPath,
                // exclude: /node_modules/
            },
            // 圖片 - 考慮 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'
        }),

        // 忽略 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: {
            chunks: 'all',
            /**
             * initial 入口chunk,對於異步導入的文件不處理
                async 異步chunk,只對異步導入的文件處理
                all 全部chunk
             */

            // 緩存分組
            cacheGroups: {
                // 第三方模塊
                vendor: {
                    name: 'vendor', // chunk 名稱
                    priority: 1, // 權限更高,優先抽離,重要!!!
                    test: /node_modules/,
                    minSize: 0,  // 大小限制
                    minChunks: 1  // 最少複用過幾次
                },

                // 公共的模塊
                common: {
                    name: 'common', // chunk 名稱
                    priority: 0, // 優先級
                    minSize: 0,  // 公共模塊的大小限制
                    minChunks: 2  // 公共模塊最少複用過幾次
                }
            }
        }
    }
})

  • paths.js
const path = require('path')

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

module.exports = {
    srcPath,
    distPath
}

  1. 對於 webpack 性能優化,優化構建速度,DllPlugin 動態鏈接庫插件,代碼如下所示:
  • 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/
            },
        ]
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: path.join(srcPath, 'index.html'),
            filename: 'index.html'
        })
    ]
}

  • webpack.dev.js
const path = require('path')
const webpack = require('webpack')
const { smart } = require('webpack-merge')
const webpackCommonConf = require('./webpack.common.js')
const { srcPath, distPath } = require('./paths')

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

module.exports = smart(webpackCommonConf, {
    mode: 'development',
    module: {
        rules: [
            {
                test: /\.js$/,
                loader: ['babel-loader'],
                include: srcPath,
                exclude: /node_modules/ // 第二,不要再轉換 node_modules 的代碼
            },
        ]
    },
    plugins: [
        new webpack.DefinePlugin({
            // window.ENV = 'production'
            ENV: JSON.stringify('development')
        }),
        // 第三,告訴 Webpack 使用了哪些動態鏈接庫
        new DllReferencePlugin({
            // 描述 react 動態鏈接庫的文件內容
            manifest: require(path.join(distPath, 'react.manifest.json')),
        }),
    ],
    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 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 域名),這裏暫時用不到
    },
    plugins: [
        new webpack.DefinePlugin({
            // window.ENV = 'production'
            ENV: JSON.stringify('production')
        })
    ]
})

  • paths.js

const path = require('path')

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

module.exports = {
    srcPath,
    distPath
}

  • 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'),
    }),
  ],
}
  1. 對於 babel,分爲環境配置、基本配置babel-polyfill、babel-runtime,如下所示:
  • 對於基本配置,環境搭建,.babelrc 配置,presetsplugins
  • Polyfillcore-jsregenerator,babel-polyfill 即兩者的結合。但是在 Babel 7.4 之後就棄用 babel-polyfill,推薦直接使用 core-jsregenerator,但是面試仍然會考察 babel-polyfill
  • babel-polyfill 按需引入,文件較大,只有一部分功能,無需全部引入,配置按需引入
  • babel-polyfill 存在的問題,會污染全局環境。如果做一個獨立的 web 系統,則無礙。如果做一個第三方 lib,則會有問題,可以使用 babel-runtime
  1. 對於 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/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",
            {
                "useBuiltIns": "usage",
                "corejs": 3
            }
        ]
    ],
    "plugins": [
        [
            "@babel/plugin-transform-runtime",
            {
                "absoluteRuntime": false,
                "corejs": 3,
                "helpers": true,
                "regenerator": true,
                "useESModules": false
            }
        ]
    ]
}
  • index.js
const sum = (a, b) => a + b

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

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

// 語法,符合 ES5 語法規範
// 不處理模塊化(webpack)

// 污染全局環境
// window.Promise1 = function() {}
// Array.prototype.includes1 = function () {}

// 使用方
// window.Promise = 'abc'
// Array.prototype.includes = 100

二、webpack 的面試題

  1. 前端爲何要進行打包和構建,答案如下所示:
  • 體積更小,Tree-shaking、壓縮和合並,加載更快
  • 編譯高級語言或語法,TSES6+、模塊化、scss
  • 兼容性和錯誤檢查,Polyfill、postcss、eslint
  • 統一、高效的開發環境
  • 統一的構建流程和產出標準
  • 集成公司構建規範,提測、上線等
  1. module、chunk、bundle 的區別,答案如下所示:
  • module 是各個源碼文件,webpack 中一切皆模塊
  • chunk 是多模塊合併成的,如 entery、import()、splitChunk 等等
  • bundle 是最終輸出的文件
  1. loaderplugin 的區別,答案如下所示:
  • loader 模塊轉換器,如 less -> css
  • plugin 擴展插件,如 HtmlWebpackPlugin
  1. babelwebpack 的區別,答案如下所示:
  • babelJS 新語法編譯工具,不關心模塊化
  • webpack 是打包構建工具,是多個 loader、plugin 的集合
  1. 如何產出一個 lib,答案如下所示:
  • 參考 webpack.dll.js
  • output.library
  • lib 的文件名,輸出 libdist 目錄下,lib 的全局變量名
  1. babel-polyfillbabel-runtime 的區別,答案如下所示:
  • babel-polyfill 會污染全局
  • babel-runtime 不會污染全局
  • 產出第三方 lib 要用 babel-runtime
  1. webpack 如何實現懶加載,答案如下所示:
  • import()
  • 結合 Vue、React 異步組件
  • 結合 Vue-router、React-router 異步加載路由
  1. 爲何 Proxy 不能被 Polyfill,答案如下所示:
  • Class 可以用 function 模擬
  • Promise 可以用 callback 來模擬
  • Proxy 的功能用 Object.defineProperty 無法模擬
  1. 對於 webpack 優化構建速度,可用於生產環境的,答案如下所示:
  • 優化 babel-loader
  • IgnorePlugin
  • noParse
  • happyPack
  • ParallelUglifyPlugin
  1. 對於 webpack 優化構建速度,不用於生產環境的,答案如下所示:
  • 自動刷新
  • 熱更新
  • DllPlugin
  1. 對於 webpack 優化產出代碼,答案如下所示:
  • 小圖片 base64 編碼
  • undlehash
  • 懶加載
  • 提取公共代碼
  • 使用 CDN 加速
  • IgnorePlugin
  • 使用 production
  • Scope Hosting
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章