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

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

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`)
});
相关推荐
理想不理想v2 小时前
webpack最基础的配置
前端·webpack·node.js
Domain-zhuo8 小时前
如何利用webpack来优化前端性能?
前端·webpack·前端框架·node.js·ecmascript
初学者7.8 小时前
Webpack学习笔记(2)
笔记·学习·webpack
理想不理想v9 小时前
webpack如何自定义插件?示例
前端·webpack·node.js
森叶11 小时前
【附源码】Electron Windows桌面壁纸开发中的 CommonJS 和 ES Module 引入问题以及 Webpack 如何处理这种兼容
webpack·electron
初学者7.13 小时前
Webpack学习笔记(3)
笔记·学习·webpack
Byron Loong13 小时前
Python+OpenCV系列:【打卡系统-工具模块设计】工具模块深度揭秘,考勤智能化的核心秘籍!
python·opencv·webpack
初学者7.2 天前
Webpack学习笔记(4)
学习·webpack
理想不理想v3 天前
免登陆是什么?
服务器·前端·javascript·vue.js·webpack