全栈里程碑二:前端基础建设

文档知识来源:抖音 "哲玄前端",《大前端全栈实践课》

webpack搭建过程

lua 复制代码
|
Webpack
    | 
    | webpack.base.js ---基础的一些配置
    | webpack.dev.js ----本地开发的一些配置
    | webpack.prod.js ----生产环境的一些配置
|    
dev.js
prod.js

Webpack 工程化常用配置和工具

1. 基本配置文件(webpack.base.js

Webpack 的构建配置文件通常包括以下几个部分:

  • 入口(Entry) :指定应用的入口文件,Webpack 将从这个文件开始,分析依赖关系并打包。
  • 输出(Output) :定义打包后的文件输出路径和文件名。
  • 模块加载(Modules) :配置如何处理不同类型的文件(使用 loaders 处理,例如:JS 文件、CSS 文件、图片等)。
  • 插件(Plugins) :使用插件对构建过程进行增强(例如:压缩代码、生成 HTML 文件、提取 CSS 等)。
  • 模式(Mode) :定义构建模式,如开发模式(development)和生产模式(production),每种模式下,Webpack 会自动应用不同的优化。
2. 常见 Loader 和 Plugin
  • Loaders:用于处理文件,转换文件内容,使其能够被 Webpack 理解和打包。

    • babel-loader:用于将 ES6/ES7+ 转换为兼容浏览器的 JavaScript。
    • css-loader:用于处理 CSS 文件,将其转换为 JavaScript 模块。
    • style-loader:将 CSS 插入到 DOM 中。
    • file-loaderurl-loader:用于处理图像和字体等文件,打包成 URL 或将文件嵌入到代码中。
  • Plugins:插件扩展了 Webpack 的功能,帮助优化构建结果。

    • HtmlWebpackPlugin:用于生成 HTML 文件,并自动将打包后的 JS 文件注入到 HTML 中。
    • MiniCssExtractPlugin:提取 CSS 到独立的文件中,防止将 CSS 嵌入到 JavaScript 中。
    • TerserWebpackPlugin:用于压缩和优化 JavaScript 代码。
    • CleanWebpackPlugin:清理构建输出目录,删除旧的文件。
3. 代码分割(Code Splitting)

Webpack 提供了 代码分割功能,将大文件拆分成多个小文件,以实现更高效的加载。常见的代码分割方式包括:

  • 入口点分割:根据入口点拆分不同的文件。
  • 按需加载(Lazy Loading) :动态加载模块,仅在需要时才加载相关代码。
  • 公共模块提取:提取多个模块之间共享的代码,减少重复加载。

借鉴一位大佬的理解:

要理解什么chunks,首先要搞清楚modulechunkbundle三者的关系。
webpack 的作用是将应用程序中的各种资源,作为模块进行管理,并将它们打包成一个或多个优化后的文件。那么我们可以理解为未处理前的各种资源模块就是 module,而打包处理过后的产物就是bundle

对于打包产物bundle 有些情况下,我们觉得太大了。 为了优化性能,比如快速打开首屏,利用缓存等,我们需要对bundle进行以下拆分,对于拆分出来的东西,我们叫它chunk

具体代码--webpack.base.js---splitChunks

js 复制代码
    optimization: {
        /**
         *   哲哥分包策略
         *   把 js 文件打包成3种类型
         *  1. vendor : 第三方 lib ,基本不会动,除非依赖版本升级
         *  2. common : 业务组件代码的公共部分抽取出来,改动较少
         *  3. entry.{page}: 不同页面 entry 里的业务组件代码的差异部分,会经常改动
         *  目的:把改动和引用评率不一样的 js 区分出来,已达到更好利用浏览器缓存的效果
         */
        splitChunks: {
            chunks: 'all',// 分割代码块的范围,对同步和异步模块都进行分割,
            maxAsyncRequests: 10, // 每次异步加载的最大进行请求数,单位为字节
            maxInitialRequests: 10,// 入口点的最大并行请求数
            cacheGroups: {
                vendor: { // 第三方依赖库
                    test:/[\\/]node_modules[\\/]/, // 打包node_module 中的文件
                    name: 'vendor', // 模块名称
                    priority: 20, // 优先级,数字越大,优先级越高
                    enforce: true, // 强制执行
                    reuseExistingChunk: true,//复用已有的公共模块chunk
                },
                common: { //公共模块
                    name: 'common', // 模块名称
                    minChunks: 2 ,// 被两处引用 即被归为公共模块
                    minSize: 1,// 最小分割文件大小(1 byte)
                    priority: 10,// 优先级
                    reuseExistingChunk: true,// 复用已有的公共 chunk
                }
            }
        },
4. Tree Shaking

Webpack 通过 Tree Shaking 特性,能够删除 JavaScript 中未使用的代码,只保留有效部分。这是 Webpack 在生产环境中进行优化的重要手段,能够大幅减小最终输出文件的大小。

5. 热更新(Hot Module Replacement,HMR)

通过 Webpack 提供的 HMR,开发过程中,当代码发生变化时,页面无需重新加载,就能直接更新相应的模块,提高开发效率。

热更新是指在开发环境中,当代码发生变化(如保存文件)时,浏览器可以在不刷新整个页面的情况下,动态地更新变化的模块

当用户在开发保存时,可以被服务器监听的到;【Webpack 使用 webpack-dev-middlewarewebpack-hot-middleware 实现对文件变化的监听。】

监听到之后,通知解析引擎(编译--模块打包--压缩优化),【文件变化后,Webpack 的解析引擎会重新编译和打包,只处理变化的模块,而不是重新打包整个应用。】

产出落地的 --具体文件的tpl,其他内容放到内存里面,这里的内容就会有变化,【在开发环境下,Webpack 使用内存存储编译结果,避免频繁的磁盘 I/O,提升速度。】

这时,就会去通知浏览器,这边有更新;【webpack-hot-middleware 会通过 WebSocket 或 HTTP 通信的方式,通知浏览器有更新内容。】

然后浏览器就会通过http方式, 把这些东西(js/css)重新请求过来,然后重新刷新页面;【浏览器接收到通知后,动态加载变化的模块(例如更新的 JS/CSS),实现部分刷新,而无需重新加载整个页面。】

express---webpack-dev-middleware --时刻监听着我在本地业务代码的变化,一旦变化,就会通知

express---webpack-hot-middleware--监听内存js/css是否有更新,一旦有更新就会通知浏览器

浏览器响应---(配套的与上面webpack-hot-middleware)浏览器就会接收

【HotModuleReplacementPlugin()】---HMR客户端

6. 缓存优化

Webpack 提供了缓存优化的机制,通过合理配置文件名哈希值(例如:[contenthash])来确保文件在更改时能够更新版本,避免浏览器缓存问题。

webpack 代码

下面是webpack相关配置的代码

webpack.base.js

入口entry,模块解析配置module,输出的路径output,配置一些插件plugins,打包的优化optimization

js 复制代码
const glob = require('glob');
const path = require('path');
const webpack = require('webpack');
const { VueLoaderPlugin } = require('vue-loader');
const HtmlWebpackPlugin = require('html-webpack-plugin');

//动态构造 pageEntries  HtmlWebpackPluginList
const pageEntries = {};
const HtmlWebpackPluginList = [];
// 获取 app/pages 目录下所有入口文件(entry.xxx.js)
const entryList = path.resolve(process.cwd(),'./app/pages/**/entry.*.js');
glob.sync(entryList).forEach(file => {
    const entryName = path.basename(file,'.js');
    // console.log('ddd',entryName,file);// entry.page1   /Users/apple/Desktop/webnew/elips/app/pages/page1/entry.page1.js
    // 构造 entry 
    pageEntries[entryName] = file;
    
    // 构造最终渲染的页面文件
    HtmlWebpackPluginList.push(
        // html-webpackplugin 辅助注入打包后的 bundle 文件到tpl文件中
        new HtmlWebpackPlugin({
            // 产物(最终模板) 输出路径
            filename: path.resolve(process.cwd(),'./app/public/dist/',`${entryName}.tpl`),
            // 指定要使用的模板文件
            template: path.resolve(process.cwd(),'./app/view/entry.tpl'),
            // 要注入的代码快(这里需要和entry里的对应)
            chunks: [entryName],
        })
    )
});



/**
 * webpack 基础配置
 */
module.exports = {
    // 入口配置--->单页面,一个入口--entry: 'index',
    // 多入口配置
    entry: pageEntries,
    // 模块解析配置(决定了要加载解释哪些模块,以及用什么方式去解释)--各种loader,例如css-loader/babel-loader
    module: {
        rules: [
            {
                test: /\.vue$/,
                use: {
                    loader: 'vue-loader'
                }
            },{
                test: /\.js$/,
                include:[
                    //只对业务代码进行 babel, 加快 webpack 打包速度
                    path.resolve(process.cwd(),'./app/pages')
                ],
                use: {
                    loader: 'babel-loader'
                },
            },{
                test: /\.(png|jpe?g|gif)(\?.+)?$/,
                use: {
                    loader: 'url-loader',
                    options: {
                        limit: 300,
                        esModule: false
                    }
                }
            },{
                test: /\.css$/,
                use: ['style-loader','css-loader'],
            },{
                test: /\.less$/,
                use: ['style-loader','css-loader','less-loader'],
            },{
                test: /\.(eoy|svg|ttf|woff|woff2)(\?\S*)?$/,
                use: 'file-loader'
            }
        ]
    },
    // 产出输出路径,因为开发和生产环境不一致,所以需要再各自的文件里面进行配置
    output: {},
    // 配置模块解析的具体行为(定义webpack 在打包时,如何找到并解析具体模块的路径)
    //例如 imprort {xxxx} from './app/pages/xxx/xxx.js'或者//imprort {xxxx} from '../xxx.vue' ,找这种文件的后缀的一种配置
    // 配置了alias之后,import {xxx} from '$pages/xxx/xxx.js'
    resolve: {
        extensions: ['.js','.vue','.less','.css'],
        alias:{
            $pages: path.resolve(process.cwd(),'./app/pages'),
            $common: path.resolve(process.cwd(),'./app/pages/common'),
            $wiggets: path.resolve(process.cwd(),'./app/pages/wiggets'),
            $store: path.resolve(process.cwd(),'./app/pages/store'),
        },//别名
    },
    // 配置 webpack 插件
    plugins: [
        // 处理 .vue 文件,这个插件是必须的
        // 它的只能是将你定义过的其他规则复制并应用到 .vue 文件里
        // 例如,如果有一条匹配规则 /\.js/ 的规则,那么他会应用到 .vue 文件中的 <script> 模板中
        new VueLoaderPlugin(),
        // 第三方组件 把第三方库暴露到 window context 下
        new webpack.ProvidePlugin({
            Vue: 'vue',
            axios: 'axios',
            _: 'lodash'
        }),
        // 定义全局常量
        new webpack.DefinePlugin({
            _VUE_OPTIONS_API_: 'true',// 支持 vue 解析 optionsApi
            _VUE_PROD_DECTOLLS_: 'false',// 禁用 Vue 调试工具
            _VUE_PROD_HYORATION_MISMATCH_DETAILS_: 'false' ,// 禁用生产环境显示 "水合"信息

        }),
        // 构造最终渲染的页面模板
        /**
         * 工程化打包的过程中,
         * 1. path.resolve(process.cwd(),'./app/view/entry.tpl') 找到这个tpl
         * 2. 找到 chunks: [entry.page2]的代码块,将 entry.page2 的代码块注入到 root 的 vue 的模板里面
         * 3. 将注入好的产物,吐出到 filename: path.resolve(process.cwd(),'./app/public/dist/','entry.page2.tpl'), 这个路径下 ,文件名为 entry.page2.tpl
         */
        ...HtmlWebpackPluginList
    ],
    // 配置打包输出优化(代码分割,模块合并,缓存,TreeShaing,压缩等优化策略)
    // 为何要分包,是因为 如果多个文件(A、B)引用了同一个文件(C)内容是,这时打包只会是在A的里面有C,和B的里面有C,这样导致C重复
    // 所以有了分包的概念,也就是把相同的内容就行提炼
    optimization: {
        // 在做分包,对产出的文件进行分包-splitChunks 这个的配置,是有你自己可以配置的
        /**
         *   哲哥分包策略
         *   把 js 文件打包成3种类型
         *  1. vendor : 第三方 lib ,基本不会动,除非依赖版本升级
         *  2. common : 业务组件代码的公共部分抽取出来,改动较少
         *  3. entry.{page}: 不同页面 entry 里的业务组件代码的差异部分,会经常改动
         *  目的:把改动和引用评率不一样的 js 区分出来,已达到更好利用浏览器缓存的效果
         */
        splitChunks: {
            chunks: 'all',// 对同步和异步模块都进行分割
            maxAsyncRequests: 10, // 每次异步加载的最大进行请求书
            maxInitialRequests: 10,// 入口点的最大并行请求数
            cacheGroups: {
                vendor: { // 第三方依赖库
                    test:/[\\/]node_modules[\\/]/, // 打包node_module 中的文件
                    name: 'vendor', // 模块名称
                    priority: 20, // 优先级,数字越大,优先级越高
                    enforce: true, // 强制执行
                    reuseExistingChunk: true,//复用已有的公共模块chunk
                },
                common: { //公共模块
                    name: 'common', // 模块名称
                    minChunks: 2 ,// 被两处引用 即被归为公共模块
                    minSize: 1,// 最小分割文件大小(1 byte)
                    priority: 10,// 优先级
                    reuseExistingChunk: true,// 复用已有的公共 chunk
                }
            }
        },
        // 将webpack 运行时生成的代码打包到 runtime.js
        runtimeChunk: true,
    },
}

webpack.dev.js

本地环境内的配置 mode,detoool(便于开发环境调试代码),开发环境输出路径,开发阶段的插件(热更新/开发环境需要一直保存运行的状态)

js 复制代码
const path = require('path');
const merge = require('webpack-merge');
const webpack = require('webpack');

// 基类配置
const baseConfig = require('./webpack.base.js');

//dev-server 配置
const DEV_SERVER_CONFIG = {
    HOST: '127.0.0.1',
    PORT: 9002,
    HMR_PATH: '__webpack_hmr',//官方规定
    TIMEOUT: 20000
};

// 开发阶段的 entry 配置需要加入 hmr
Object.keys(baseConfig.entry).forEach(v =>{
    // 第三方包不作为 hmr入口
    if(v !== 'vendor'){ 
        baseConfig.entry[v] = [
            // 主入口文件
            baseConfig.entry[v],
            // Hmr 更新入口,官方指定的 hmr 路径
            // 这里做的就是HMR客户端(注入代码),也就是浏览器和webpack-hot-middleware,需要建立连接
            `webpack-hot-middleware/client?path=http://${DEV_SERVER_CONFIG.HOST}:${DEV_SERVER_CONFIG.PORT}/${DEV_SERVER_CONFIG.HMR_PATH}&timout=${DEV_SERVER_CONFIG.TIMEOUT}&reload=true`
        ]
    }
})

// 本地环境 webpack 配置
const webpackConfig = merge.smart(baseConfig,{
    // 指定开发环境模式
    mode: 'development',
    // source-map 开发工具,呈现代码的映射关系,便于在开发过程中调式代码
    devtool: 'eval-cheap-module-source-map',
    // 开发环境的output配置
    output: {
        filename: 'js/[name]_[chunkhash:8].bundle.js',
        path: path.resolve(process.cwd(),'./app/public/dist/dev/'), // 输入文件存储路径
        publicPath: `http://${DEV_SERVER_CONFIG.HOST}:${DEV_SERVER_CONFIG.PORT}/public/dist/dev/`,// 外部资源公共路径引用
        globalObject: 'this', // 指定全局对象为 `this`,使代码在不同的执行环境下(如浏览器和 Node.js)都能正确工作
    },
    // 开发阶段插件
    plugins:[
        // HotModuleReplacementPlugin 用于实现热更新模块替换 ( Hot Module Replacement 简称HMR)
        // 模块热更新允许在应用程序运行时替换模块
        // 极大的提升开发效率,因为能让应用程序一直保持运行状态
        new webpack.HotModuleReplacementPlugin({
            multiStep: false,
        })
    ]
});

module.exports = {
    // webpack  配置
    webpackConfig,
    // devserver 配置,暴露给 dev.js 使用
    DEV_SERVER_CONFIG
};

webpack.prod.js

js 复制代码
const path = require('path');
const merge = require('webpack-merge');
const os = require('os');
const HappyPack = require('happypack'); // 处理多线程的

const MinCssExtractPlugin = require('mini-css-extract-plugin');
const CleanWebpcckPlugin = require('clean-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const HtmlWebpackInjectAttributesPlugin = require('html-webpack-inject-attributes-plugin');
const TerserWebpackPlugin = require('terser-webpack-plugin');

// 基类配置
const baseConfig = require('./webpack.base.js');

// 多想层 build 设置
const happypackCommonConfig = {
    debug: false,
    // 拿到  os.cpus().length 的数量,拿到多少就开多少核的线程
    threadPool: HappyPack.ThreadPool({ size: os.cpus().length })
}

// 生产环境 webpack 配置
const webpackConfig = merge.smart(baseConfig, {
    // 指定生产环境模式
    mode: 'production',
    // 生产环境的output配置
    output: {
        filename: 'js/[name]_[chunkhash:8].bundle.js',
        path: path.join(process.cwd(), './app/public/dist/prod'),//产生具体文件的路径
        publicPath: '/dist/prod',
        crossOriginLoading: 'anonymous',
    },
    module: {
        rules: [{
            test: /\.css$/,
            use: [
                MinCssExtractPlugin.loader,
                'happypack/loader?id=css'
            ]
        }, {
            test: /\.js$/,
            include: [
                //只对业务代码进行 babel, 加快 webpack 打包速度
                path.resolve(process.cwd(), './app/pages')
            ],
            use: ['happypack/loader?id=js']
        }]
    },
    // webpack 不会有大量 hints 信息,默认为 warning
    performance: {
        hints: false,
    },
    plugins: [
        // 每次 build 前,清空 piblic/dist 目录
        new CleanWebpcckPlugin(['pubilc/dist'],{
            root: path.resolve(process.cwd(),'./app/'),
            exclude:[],// 不排除任何文件
            verbose: true,// 输出详细日志
            dry: false, // 为false,不进入干运行模式,实际删除文件;为true,插件将进入 "dry-run" 模式,即模拟执行清理操作,但实际上并不会删除任何文件
        }),
        // 提取 css 的公共部分,有效利用缓存,(非公共部分使用 inline)
        new MinCssExtractPlugin({
            chunkFilename: 'css/[name]_[comtenthash:8].bundle.css'
        }),
        // 优化并压缩 css 资源
        new CssMinimizerPlugin(),
        // 多线程打包 js ,加快打包速度
        new HappyPack({
            ...happypackCommonConfig,
            id: 'js',
            loaders: [`babel-loader?${JSON.stringify({
                presets: ['@babel/preset-env'],
                plugins: ['@babel/plugin-transform-runtime']
            })}`]
        }),
        // 多线程打包 CSS , 加快打包速度
        new HappyPack({
            ...happypackCommonConfig,
            id: 'css',
            loaders: [`babel-loader?${JSON.stringify({
                path: 'css-loader',
                options: {
                    importLoader: 1
                }
            })}`]
        }),
        // 浏览器在请求资源时不发送用户的身份凭证
        new HtmlWebpackInjectAttributesPlugin({
            crossorigin: 'anonymous'
        })
    ],
    optimization: {
        // 使用 TerserWebpackPlugin 的并发和缓存,提升压缩简单性的性能
        // 清楚 console.log
        minimize: true,
        minimizer: [
            new TerserWebpackPlugin({
                cache: true, //启用缓存来加快构建过程
                parallel: true, // 利用多核 cpu 的优势来加快压缩速度
                terserOptions: {
                    compress:{
                        drop_console: true,// 去掉 console.log 内容
                    }
                }
            })
        ]
    }
});

module.exports = webpackConfig;

dev.js

js 复制代码
// 本地开发启动 devaServer
const express = require('express');
const path  = require('path');
const consoler = require('consoler'); //打印日志的,有颜色高亮的效果
const webpack = require('webpack');
const devMiddleware = require('webpack-dev-middleware');
const hotMiddleware = require('webpack-hot-middleware');

//从webpack.dev.js 获取 webpack 配置 和 devserver 配置
const { webpackConfig, DEV_SERVER_CONFIG } = require('./config/webpack.dev.js');

const app = express();

const compiler = webpack(webpackConfig);

// 指定静态文件目录
app.use(express.static(path.join(__dirname,'../public/dist')));

// 引用 devMiddleware 中间件 (监控文件改动)
app.use(devMiddleware(compiler,{
    // 落地文件--不打包到内存里面
    writeToDisk: (filePath) =>{ return filePath.endsWith('.tpl')},

    // 资源路径 -- 与webpack.dev.js的output的publicPath路径相对应
    publicPath: webpackConfig.output.publicPath,

    // headers 配置 --可以访问跨域的内容
    headers: {
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Allow-Methods': 'GET, POST, DELETE, PATCH, OPTIONS',
        'Access-Control-Allow-Headers': 'X-Require-With, content-type, Authorization',
    },
    stats:{ colors: true}

}));

// 引用 hotMiddleware 中间件 (实现热更新通讯)
app.use(hotMiddleware(compiler,{
    path: `/${DEV_SERVER_CONFIG.HMR_PATH}`,
    log: () =>{}
}))


consoler.info('请等待webpack初次构建完成提示...')


// 启动服务 devServer ---- npm run build:dev
const port = DEV_SERVER_CONFIG.PORT;
app.listen(port,() =>{
    console.log(`app listening on port ${port}`);
    
})

prod.js

js 复制代码
const webpack = require('webpack');
const webpackProdConfig  = require('./config/webpack.prod.js');

console.log('\nbuilding... \n');

webpack(webpackProdConfig,(err,stats) =>{
    if (err) { 
        console.log('err build... \n',err);
        return;
    }
    //打包过程中出现的问题打印
    process.stdout.write(`${stats.toString({
        colors: true, // 在控制台输出色彩信息
        modules: false, // 不显示每个模块的打包信息
        children: false, // 不显示子编译任务信息
        chunks: false, // 不显示每个代码快的信息
        chunkModules: true, //显示代码快中模块的信息
    })}\n`)
});
相关推荐
垣宇13 小时前
Vite 和 Webpack 的区别和选择
前端·webpack·node.js
小纯洁w1 天前
Webpack 的 require.context 和 Vite 的 import.meta.glob 的详细介绍和使用
前端·webpack·node.js
海盗强2 天前
Webpack打包优化
前端·webpack·node.js
祈澈菇凉2 天前
如何优化 Webpack 的构建速度?
前端·webpack·node.js
懒羊羊我小弟2 天前
常用 Webpack Plugin 汇总
前端·webpack·npm·node.js·yarn
祈澈菇凉3 天前
Webpack的持久化缓存机制具体是如何实现的?
前端·webpack·gulp
懒羊羊我小弟4 天前
Webpack 基础入门
前端·webpack·rust·node.js·es6
刽子手发艺4 天前
Selenium+OpenCV处理滑块验证问题
opencv·selenium·webpack
懒羊羊我小弟4 天前
常用Webpack Loader汇总介绍
前端·webpack·node.js
真的很上进6 天前
【1.8w字深入解析】从依赖地狱到依赖天堂:pnpm 如何革新前端包管理?
java·前端·vue.js·python·webpack·node.js·reactjs