前端工程化的理解

模块化

什么是模块化开发,为什么要使用它?

模块化开发,字面意思就是分模块进行开发。

javascript 复制代码
// example-one.js
var age = 18;
// example-two.js
var age = 20
​
// index.html
<script src="example-one.js"></script>
<script src="example-two.js"></script>
<script>
  console.log(age);
</script>

在没有模块化开发的年代,多人开发时,同名变量会受到干扰,所以就有了模块化开发AMD、UMD。

当然AMD和UMD不重点讲述,主要聊的是常用的 CommonJSES Module

聊聊 CommonJS 和 ES Module

CommonJS:

  • 语法 :使用 require() 导入模块,module.exportsexports 导出模块。
  • 场景:Node.js 默认模块系统,适用于服务端开发(同步加载)。
  • 示例
ini 复制代码
// math.js 导出模块
module.exports = { add: (a, b) => a + b };
​
// index.js 导入模块
const math = require('./math.js');
math.add(2, 3);

ES Module

  • 语法 :使用 import 导入模块,export 导出模块。
  • 场景:浏览器原生支持及现代前端工程化项目(异步加载)。
  • 示例
csharp 复制代码
// math.js 导出模块
export const add = (a, b) => a + b;
​
// index.js  导入模块
import { add } from './math.js';
add(2, 3);

区别:

CommonJS 将文件内的变量值拷贝给 module.exports 导出一个对象,使用者可以按需加载该文件,它是一个同步操作,后续内容需要等待它完成。它是有缓存的,其缓存是根据 loaded 属性是否为 true 判断。

ESmodule 先进行分析文件,在文件头部根据 import 解析相关内容,进行fetch获取,形成模块记录,然后模块实例化分配空间,将导入和导出的内存地址相连接,最后运行代码,将值填充到内存中。

构建与打包

什么是构建与打包?为什么需要它们?
  • 构建:对代码进行编译、转换、优化等操作(如将TypeScript转成JavaScript、Sass转成CSS)。
  • 打包:将多个分散的代码文件合并为少数几个文件(或按需分块),解决依赖关系并优化资源加载。
常用的构建打包工具有哪些?具体使用?

常见工具与场景

工具 特点 适用场景
Webpack 高度可配置,插件生态丰富 复杂SPA、需深度定制化
Rollup 面向库的打包,Tree Shaking效率高 开发开源库、组件库
Vite 基于ESM的按需编译,开发模式极速热更新 现代框架(Vue/React)项目
聊下webpack 的内容以及它的原理和使用?

一、Webpack 的定义与定位

Webpack 是一个静态模块打包工具,它将项目中的所有资源(JS、CSS、图片等)视为模块,通过依赖关系构建依赖图(Dependency Graph),最终生成优化后的静态资源包。

核心目标

  1. 模块化支持:处理 CommonJS、ES Modules、AMD 等模块规范
  2. 代码转换:通过 Loader 编译非 JS 资源(如 TypeScript → JS,Sass → CSS)
  3. 性能优化:代码压缩、Tree Shaking、代码分割
  4. 生态扩展:通过插件系统实现功能增强(如 HTML 生成、环境变量注入)

二、原理和使用

  1. 项目中的拆分

在项目中,我们通常分为三个文件:

基础配置(webpack.base.js)

开发配置(webpack.dev.js)

生产配置(webpack.prod.js)

  1. 入口(Entry)

入口主要是打包的起点文件,Webpack 从入口开始递归分析依赖。

java 复制代码
// webpack.base.js
module.exports = {
  entry: './src/index.js' // 单入口
  // 多入口:entry: { app: './src/app.js', admin: './src/admin.js' }
};
  1. 输出(Output)

指定打包后的文件存储位置和命名规则。

arduino 复制代码
// webpack.dev.js 开发环境的 output 配置  产物输出路径,因为开发和生产环境输出不一致,所以在各自环境中自行配置
  
// devServer 配置
const DEV_SERVER_CONFIG = {
  HOST: '127.0.0.1',
  PORT: 9002,
  HMR_PATH: '__webpack_hmr', // 官方规定
  TIMEOUT: 20000
}
​
module.exports = {
  output: {
    filename: 'js/[name]_[chunkhash:8].bundle.js',
    path: path.join(process.cwd(), './app/public/dist/dev/'), // 输出文件存储路径(自定义)
    publicPath: `http://${DEV_SERVER_CONFIG.HOST}:${DEV_SERVER_CONFIG.PORT}/public/dist/dev/`, 
    // 自建服务器,外部资源公共路径(主要用于热更新)
    globalObject: 'this'
  }
}
​
​
// webpack.prod.js  生产环境的 output 配置
​
module.exports = {
  output: {
    filename: 'js/[name]_[chunkhash:8].bundle.js',
    path: path.join(process.cwd(), './app/public/dist/prod'), // 输出文件存储路径(自定义)
    publicPath: '/dist/prod',
    crossOriginLoading: 'anonymous'
  }
}
  1. Loader

处理非 JS 文件的转换器,类似"翻译官"。

javascript 复制代码
// webpack.base.js
module.exports = {
  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: /.(eot|svg|ttf|woff|woff2)(?\S*)?$/,
        use: 'file-loader'
      }]
    }
}
​
//  webpack.prod.js
module.exports = {
  module: {
      rules: [{
        test: /.css$/,
        use: [
          MiniCssExtractPlugin.loader,
          'happypack/loader?id=css'
        ]
      }, {
        test: /.js$/,
        include: [
          // 只对业务代码进行 babel, 加快 webpack 打包速度
          path.resolve(process.cwd(), './app/pages')
        ],
        use: [
          'happypack/loader?id=js'
        ]
      }]
  }
}
// happypack主要用于多进程打包,提高打包效率
  1. 插件(Plugins)

扩展 Webpack 功能,处理 Loader 无法实现的任务(如资源优化、环境注入)。插件可以在webpack 的任意生命周期进行注入,提高了拓展性。

php 复制代码
// webpack.base.js
// 配置 webpack 插件
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { VueLoaderPlugin } = require('vue-loader');
​
const htmlWebpackPluginList = [];
 htmlWebpackPluginList.push(
    // html-webpack-plugin 辅助注入打包后的 bundle 文件到 tpl 文件中
    new HtmlWebpackPlugin({
      // 产物(最终模版)输出路径
      filename: path.resolve(process.cwd(), './app/public/dist/', `${entryName}.tpl`),
      // 指定要使用的模块文件
      template: path.resolve(process.cwd(), './app/view/entry.tpl'),
      // 要注入的代码块
      chunks: [ entryName ]
    })
  )
// 如果是多入口打包,对所有目标文件遍历进行以上操作即可
​
module.exports = {
  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_DEVTOOLS__: 'false', // 禁用 Vue 调试工具
      __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: 'false', // 禁用生产环境显示"水合" 信息
    }),
    // 构造最终渲染的页面模版
    ...htmlWebpackPluginList
  ]
}
​
​
// webpack.dev.js
const webpack = require('webpack');
module.exports = {
  // 开发阶段插件
  plugins: [
    // HotModuleReplacementPlugin 用于实现热模块替换 ( Hot Module Replacement 简称 HMR)
    // 模块热替换允许在应用程序运行时替换模块
    // 极大的提升开发效率,因为能让应用程序一直保持运行状态
    new webpack.HotModuleReplacementPlugin({
      multiStep: false
    })
  ]
}
​
​
​
// webpack.prod.js
​
const os = require('os');
const HappyPack = require('happypack');
​
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const CSSMinimizerPlugin = require('css-minimizer-webpack-plugin');
const HtmlWebpackInjectAttributesPlugin = require('html-webpack-inject-attributes-plugin');
​
// 多线程 build 设置
const happypackCommonConfig = {
  debug: false,
  threadPool: HappyPack.ThreadPool({ size: os.cpus().length })
}
​
module.exports = {
  plugins: [
    // 每次 build 前,清空 public/dist 目录
    new CleanWebpackPlugin(['public/dist'], {
      root: path.resolve(process.cwd(), './app/'),
      exclude: [],
      verbose: true,
      dry: false
    }),
    // 提取 css 的公共部分,有效利用缓存
    new MiniCssExtractPlugin({
      chunkFilename: 'css/[name]_[contenthash: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: [{
        path: 'css-loader',
        options: {
          importLoaders: 1
        }
      }]
    }),
    // 浏览器在请求资源时不发送用户的身份凭证
    new HtmlWebpackInjectAttributesPlugin({
      crossorigin: 'anonymous'
    })
  ]
}
​
  1. 分包(Chunk)与其他优化

根据入口和代码分割原则,将模块分组为chunk。丑化压缩代码,减少没必要的损耗。

javascript 复制代码
// webpack.base.js
// 配置打包输出优化(配置代码分割,模块合并,缓存,TreeShaking,压缩等优化策略)
module.exports = {
  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_modules 中的文件
          name: 'vendor', // 模块名称
          priority: 20, // 优先级,数字越大,优先级越高
          enforce: true, // 强制执行
          reuseExistingChunk: true, // 复用已有的公共 chunk
        },
        common: { // 公共模块
          name: 'common', // 模块名称
          minChunks: 2, // 被两处引用即被归为公共模块
          minSize: 1, // 最小分割文件的大小(1 byte)
          priority: 10, // 优先级
          reuseExistingChunk: true, // 复用已有的公共 chunk
        }
      }
    }
  }
}
​
​
​
// webpack.prod.js
const TerserWebpackPlugin = require('terser-webpack-plugin');
module.exports = {
  optimization: {
    // 使用 TerserPlugin 的并发和缓存,提升压缩阶段的性能
    // 清除 console.log
    minimize: true,
    minimizer: [
      new TerserWebpackPlugin({
        cache: true, // 启动缓存来加载构建过程
        parallel: true, // 利用多核 CPU 的优势来加快压缩速度
        terserOptions: {
          compress: {
            drop_console: true, // 去掉 console.log 内容
          }
        }
      })
    ]
  }
}
​
​
  1. 热更新(HMR)

热模块替换(Hot Module Replacement, HMR) 是 Webpack 提供的一项功能,允许在应用运行时动态替换、添加或删除模块,无需完全刷新页面。它能保留应用状态(如表单输入、路由位置),显著提升开发体验。

javascript 复制代码
// 本地开发启动 devServer
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 {
  // webpack 配置
  webpackConfig,
  // devServer 配置,暴露给dev.js 使用
  DEV_SERVER_CONFIG
} = require('./config/webpack.dev');
​
const app = express();
​
const compiler = webpack(webpackConfig);
​
// 指定静态文件目录
app.use(express.static(path.join(__dirname, '../public/dist')));
​
// 引用 devMiddleware 中间件 (监控文件改动)
app.use(devMiddleware(compiler, {
  // 落地文件
  writeToDisk: (filePath) => filePath.endsWith('.tpl'),
​
  // 资源路径
  publicPath: webpackConfig.output.publicPath,
​
  // headers 配置
  headers: {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS',
    'Access-Control-Allow-Headers': 'X-Requested-With, content-type, Authorization',
  },
  stats: {
    colors: true
  }
}));
​
// 引用 hotMiddleware 中间件(实现热更新通讯)
app.use(hotMiddleware(compiler, {
  path: `/${DEV_SERVER_CONFIG.HMR_PATH}`,
  log: () => {}
}));
​
consoler.info('请等待webpack初次构建完成提示...')
​
// 启动 devServer
const port = DEV_SERVER_CONFIG.PORT;
app.listen(port, () => {
  console.log(`app listening on port ${port}`)
});
​

从上述图与代码可以描述出,我们搭建一套express服务器 devServer,当代码发生变化时,我们将通过解析引擎重新进行解析。

devServer服务器通过 devMiddleware,监听到打包的文件发生变化。而后devServer服务器通过 hotMiddleware 通知客户端,代码发生了变化。最后客户端重新从devServer服务器获取最新代码。

总结:以上是关于工程化的部分理解,还有开发规范、CI/CD(持续集成/持续部署)、前端性能优化、监控与质量保障、微前端架构等,不在本文过多赘述。工程化给开发者带来了便捷,同时合理的使用工程化,会让项目稳固进行下去。

相关推荐
Lepusarcticus4 分钟前
《掌握 JavaScript 字符串操作,这一篇就够了!》
前端·javascript
田本初9 分钟前
vue-cli工具build测试与生产包对css处理的不同
前端·css·vue.js
inxunoffice1 小时前
批量在多个 PDF 的指定位置插入页,如插入封面、插入尾页
前端·pdf
木木黄木木1 小时前
HTML5 Canvas绘画板项目实战:打造一个功能丰富的在线画板
前端·html·html5
豆芽8191 小时前
基于Web的交互式智能成绩管理系统设计
前端·python·信息可视化·数据分析·交互·web·数据可视化
不是鱼1 小时前
XSS 和 CSRF 为什么值得你的关注?
前端·javascript
顺遂时光1 小时前
微信小程序——解构赋值与普通赋值
前端·javascript·vue.js
anyeyongzhou1 小时前
img标签请求浏览器资源携带请求头
前端·vue.js
Captaincc1 小时前
腾讯云 EdgeOne Pages「MCP Server」正式发布
前端·腾讯·mcp
最新资讯动态2 小时前
想让鸿蒙应用快的“飞起”,来HarmonyOS开发者官网“最佳实践-性能专区”
前端