从源码到dist:拆解Webpack如何完成前端工程的"基因编译"

1. 为什么需要工程转换?

  • 开发环境 vs. 运行时环境的不一致性

    • 开发时:使用现代语法(JSX/Sass/ESM)、npm 管理依赖
    • 运行时:浏览器仅支持标准 JS/CSS,无法直接识别 node_modules
    • 构建工具的作用:将开发环境的代码转换成浏览器可执行的代码
  • 工程结构的变化

    • 开发时 :模块化、依赖关系清晰(import/require

    • 运行时 :扁平化静态资源(dist 目录,含 index.html + bundle.js

arduino 复制代码
工程的转换,命令是 **npm run build** 进行打包,打包完成会生成一个 dist 目录,这个目录里面有个 html 文件,有个 js 有 css,还有 assets 文件,这就是转换的结果。

我们的工程原本是这个样子

转换之后变成了这个样子

这就是工程级别的转换。

2. 构建工具的核心职责

为什么要求转换

思考一个问题,我们为什么要去转换? 是因为我们开发和维护的代码和运行时需要的代码不一致了。

我们使用一个语言新的特性,不想去考虑兼容问题,但是运行时不能不考虑,运行时的代码就希望兼容性更好。再比如说,开发和维护的代码希望使用一些非常简便的语法,像 jsx,sass,我们需要对语言进行增强,这个写起来更舒服,生产效率更高,但是这个运行时它不支持,而运行时需要的代码是什么呢,是一个非常纯粹满足语言标准的代码,没有那些花里胡哨的代码。

因为开发和维护的代码和运行时的代码不一致,所以我们需要一个东西去转换,这是对代码层面的。同样,工程层面也是一个道理,我们开发和维护的工程和运行时的工程不一样。我们开发和维护的时候希望可以使用 npm 去安装各种第三方库,生成 node_modules,但是运行时不行,浏览器环境并不支持 npm ,所以我们运行时的工程是非常传统的,也就是说打包之后的代码它就完全脱离了开发的环境。我们打包之后的代码跟传统代码一样通过index.html 右键在浏览器上运行。

我们开发的时候的工程和运行时的工程结构不一致了,所以我们就需要找一个东西来进行这个转换,而进行转换的工具就叫做构建工具。所以构建工具是用来进行工程的转换的。

1)明确三种关键问题

  1. 哪种工程更适合开发和维护?

    • Webpack:一切皆模块(JS/CSS/图片均可 import
  2. 哪种工程更适合运行时?

    • 传统 HTML+JS+CSS 结构,可直接在浏览器运行
  3. 如何转换(打包)?

    • 依赖分析 → 代码转换(Babel/Loader)→ 合并优化

这三个问题没有标准,这就造成了在不同的需求下,从不同的角度出发,着力点不一样,这三个点的理解可能就不一样,于是就造成了各种构建工具的差异。构建工具有很多,webpack、rollup、esbuild等等,这些构建工具的本质差异是什么,其实就是对上边这三点的理解不一致。

webpack的这三个东西

  1. 哪种工程更适合开发和维护 (一切皆为模块,都可以进行导入)
  2. 哪种工程更适合运行时 (传统工程,就是最开始的html右键浏览器运行)
  3. 如何转换(打包) (以一个文件为入口点出发,去寻找他们的依赖关系,导入谁就依赖谁,然后就形成了一大堆文件,最后进行合并,把所有的 JS 文件合并在一起,把所有的 CSS 文件合并在一起,less 代码该转换转换,资源文件就单独形成各自的文件)

(2)Webpack 的打包机制

  • 依赖分析(不运行代码,而是解析 AST)

    • 支持 ESM (import)CJS (require)

    • 模块查找规则:

      • ./../ → 相对路径
      • ./ 开头 → node_modules 查找(遵循 package.jsonmain 字段)
  • 打包结果的特点

    • 无模块化语法(import/require 被替换)
    • 合并 JS/CSS(减少 HTTP 请求)
    • 文件指纹(Hash 值,优化缓存)

    weboack 的入口,在分析依赖关系的时候,具体是如何分析的呢?它分析的方式并不是去运行这个代码,而是把整个代码看成是一个字符串,webpack 是不会去运行代码的,它就是来进行打包转换的,把入口文件告诉 webpack,会把这个文件的内容读出来,读出来过后分析一下这个文件用到了哪些其他的文件。

    它是怎么知道用到了哪些其他的文件的,它就是把整个代码看成一个字符串,然后把这个字符串分解成为一个 AST(抽象语法树),然后通过抽象语法树去找到那些导入的语句,而且 webpack 是同时支持 ESM 和 CMJ 的,这就意味着在代码中即可以使用 import 来进行导入,也可以使用 require 来进行导入,它都支持。

    这就解决了一个疑惑,说浏览器环境并不支持 CMJ,不能使用 require,那么为什么在 VUE 和 React 代码里边可以使用这个 require,是因为写的 require、import 压根不是给浏览器看的,是给构建工具看的,像 webpack 它来识别导入语句,因为它两者都支持,所以两种导入语句都能写。

    无论写的哪一种,实际上都是告诉 webpack 这里边有依赖关系,然后它把依赖关系分析完之后进行打包,打包结果里边不会包含任何的导入语句。也就是说在源代码中写的import、require 在打包结果里边压根就不存在了,打包结果里面是不存在任何的模块化代码。

    到哪里去找这个依赖的文件呢,也就是模块的查找,在 webpack 中所有都是模块,哪怕一个图片都是模块,到哪里去找这个图片,去哪找这个 JS 呢? 有模块的查找规则。

    比如说 import './cover',cover 是一个文件夹,会默认的去找这个文件夹的 index.js 文件,比如说 import $ from 'jquery',目录里面没有 jquery文件夹,这又是一个查找规则,当给的路径不以'./','../'开头时,这个时候用的是 node 模块规则,看一下当前目录有没有 node_modules,然后在 node_modules 这个目录里边去寻找 jquery文件夹,又找到这个文件夹下面的package.json找到 'main'字段对应的文件,然后在这个文件找到对应的 jquery.js 文件。

3. 开发服务器(Webpack Dev Server)

  • 作用:实时编译 + 自动刷新

  • 运行机制

    1. 启动 express 服务器
    2. 内存打包 (不生成 dist,直接放在内存)
    3. 监听文件变化 → 重新编译 → 通知浏览器刷新

现在是可以进行转换了,但是该怎么运行呢,不能说每写一行代码就去运行一个命令 npm run build 把 dist 在新的工程打开把页面运行出来。能不能一边写代码一边自动运行,在 webpack 中使用的办法就是使用开发服务器

运行命令 npm run serve,运行之后会给地址。点击打开就运行出来了,就不用再去先打包然后再用 VScode 去打开这个打包结果再运行。

开发服务器(webpack serve)是由 webpack-dev-server(是webpack 的一个库) 启动的,webpack-dev-server 里面又依赖了 express 。

当我们运行 webpack serve 的时候,它会利用 webpack-dev-server 启动一个开发服务器,与此同时它会去进行打包,相当于帮我们运行了一个 npm run build,只不过这次打包是在内存里边完成,并不会把打包的结果形成文件,在内存中形成打包结果。

然后控制台会给一个提示,让去访问哪一个地址,当访问地址时就会打开浏览器,浏览器会自动的出现这个地址,由浏览器去访问开发服务器,于是浏览器向开发服务器发送请求,然后开发服务器会从内存中的打包结果中去拿到一个页面(index.html),把页面响应给浏览器。浏览器就可以看到页面了,浏览器拿到页面之后就要去渲染页面了,渲染页面的工程中会去继续请求 JS、CSS,开发服务器又回去内存中的打包结果取出相应的 JS、CSS相应给浏览器,这样一来浏览器就把整个页面运行出来了。

这就是整个过程,省略了手动的去打包,手动的去运行浏览器的过程。

这样还能实现源码变化后自动刷新的功能。是因为 webpack serve 还有个功能,它可以监听文件的变化,当文件发生变化时,它会触发重新打包,也就是说更改了内存中的打包结果。光更改没用,还得让浏览器刷新,一刷新就要重新请求,重新请求得重新拿打包结果,就拿到了新的打包结果,相应的就是新的内容。

4. 文件指纹与源码地图

(1)文件指纹(Hash)

  • 作用:确保内容不变时用缓存,内容变化时立即更新
  • 示例main.a3b4c5.js(哈希值随内容变化)

打包的 js 或 css 名称奇奇怪怪的字母数字是文件指纹,其实就是哈希值的前几位,会随着源码的内容的变化而变化的。文件指纹既可以保证文件内容没变时一直使用缓存结果,也可以保证内容变化之后立即使用最新的结果。

2)源码地图(Source Map)

  • 作用:调试时映射回原始代码(而非打包后的代码)
  • 文件.map 文件(关联 bundle.js 和源码)

源码地图:打包后'.map'后缀的,是可以让我们更好的去调试,如果没有源码地图,我们去调试时开发者工具打断点显示的代码是打包之后的代码而不是源码,源码地图让打包后的代码和源码相对应。

5. 脚手架:工程化的最后一环

vue-cli 、 vite 、 cra 、umijs 等等

复制代码
虽然有了构建工具,这些目录结构的安排得自己去组织,构建工具里面的配置得一行一行去写,各种具体的插件得自己去安装。这时候就希望有一个工具能帮我们把这些给做了,这个工具就是脚手架,脚手架是用来干什么的,就是来搭工程的。
  • 为什么需要脚手架?

    • 构建工具配置复杂(Webpack 配置、Babel、Loader)
    • 项目结构标准化(如 Vue/React 官方推荐目录)
  • 核心功能

    1. 命令行交互(选择框架、配置项)


    2. 生成标准化工程模板 (预置 webpack.config.jsbabelrc 等)

    根据你的选择,它给你提供一个工程结构,那些依赖帮我们处理好,配置帮我们考虑好
    3. 集成最佳实践 (如 Vue CLI 默认支持 SassRouter

相关推荐
程序猿阿伟8 分钟前
《不只是接口:GraphQL与RESTful的本质差异》
前端·restful·graphql
若梦plus2 小时前
Nuxt.js基础与进阶
前端·vue.js
樱花开了几轉2 小时前
React中为甚么强调props的不可变性
前端·javascript·react.js
风清云淡_A2 小时前
【REACT18.x】CRA+TS+ANTD5.X实现useImperativeHandle让父组件修改子组件的数据
前端·react.js
小飞大王6662 小时前
React与Rudex的合奏
前端·react.js·前端框架
若梦plus2 小时前
React之react-dom中的dom-server与dom-client
前端·react.js
若梦plus2 小时前
react-router-dom中的几种路由详解
前端·react.js
若梦plus2 小时前
Vue服务端渲染
前端·vue.js
Mr...Gan2 小时前
VUE3(四)、组件通信
前端·javascript·vue.js
OEC小胖胖2 小时前
渲染篇(二):解密Diff算法:如何用“最少的操作”更新UI
前端·算法·ui·状态模式·web