在终端里输入 npm start 后都发生了啥

2023年 12月 12日 136.8k 0

前言

在前面的内容纯属胡说八道,如果想要看正文,请直接滚动条往下拉,以省下宝贵的时间继续卷。

要想对 JavaScript 代码进行打包,我们可以依赖 webpack 对我们的帮助我们完成这一件事情。要想使用 webpack,首先需要我们安装 webpack,首先对项目进行初始化:

npm init -y

生成配置配置文件:

图片图片

要想使用 webpack,首先需要安装 webpack 以及 webpack-cli,这里还有 html-webpack-plugin,用于生成 html 模板,具体命令如下:

npm install  webpack webpack-cli html-webpack-plugin -D

依赖安装完成之后,我们需要在跟目录上面创建一个名为 webpack.config.js,当然,你也可以创建其他文件名的 js 文件,并添加以下配置:

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  entry: "./src/index.js",
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "./dist"),
  },
  mode: "production",
  plugins: [
    new HtmlWebpackPlugin({
      template: "./index.html",
    }),
  ],
};

为了能执行打包命令,我们在 package.json 文件中的 script 中添加这一段命令:

"build": "webpack"

如果你使用的 webpack 配置文件名为其他的则需要在该命令中添加相对应的路径,否则在执行命令的时候 webpack-cli 则无法找到相关的配置。

接下来我们创建在根目录下创建一个 src 目录,在目录下面创建一个 index.js 文件,作为整个项目的入口,并在文件中添加一些自己想写的代码:

console.log("hello webpack");

此时,在命令行中输入一下命令:

npm run build

此时文件被输出出来了:

图片图片

生成的文件是根据我们前面的 webpack 配置文件中生成的,通过运行 index.html 文件,hello webpack 也被输出在浏览器控制台上面:

图片图片

但是这个方法存在弊端,当我们对源代码进行修改的时候,它并不会对我所修改的代码进行重新编译,要想能够在浏览器中看到最新效果,你必须通过重新执行 npm run build 打包才可以,开发效率极其低下。

watch

webpack 有一个 watch 属性可以监听文件的变化,当监听到文件变化,当它们修改后会重新编译,要启用 watch,你只需要在 webpack.config.js 文件中设置 watch:true即可,详情如下:

module.exports = {
  ...,
  watch: true,
};

或者在 package.json 文件中 script 下修改添加 --watch.代码如下所示:

"build": "webpack --watch"

控制台再次执行 npm run build,你会发现这次控制台不会结束了,会一直开启着,详情请看下图:

图片图片

当我们修改文件内容的时候,watch 会监听着文件变化,如下图终端所示:

图片图片

并且浏览器上的内容也会随着变化而变化,你也可以配置 watchOptions 来使你的项目更快,例如:

watchOptions: {
  aggregateTimeout: 6000,
  ignored: /node_modules/,
},

在上面的配置中,当第一个文件更改,会在重新构建前增加延迟,它会将这段时间内的所有更改都聚合到一次重新构建中,以毫秒为单位。对于某些系统,监听大量文件会导致大量的 CPU 或内存占用。可以使用正则排除像 node_modules 如此庞大的文件夹。

尽管有这些配置,效率仍然不高,编译成功后,都会生成新的文件,并且需要开启 live-server,但是这个插件属于 vscode 的,但是在其他编辑器上并没有,并不属于 webpack。

live-server 每次都会重新刷新整个页面,并不能保存当前页面的状态,还会编译所有的代码。

webpack-dev-server的基本使用

webpack-dev-server(简称 WDS) 为你提供了一个基本的 web server,并且具有 live reloading(实时重新加载)功能。要想使用使用你首先要安装该依赖:

npm install --save-dev webpack webpack-dev-server

继续在 package.json 文件中 script 下修改添加一段命令.代码如下所示:

"start": "webpack serve --open"

完成之后,在终端下输入以下命令执行:

npm start

WDS 会自动为为你自动开启电脑上的默认浏览器,并且默认的端口为 http://localhost:8080/,在终端里有如下输出:

图片图片

在浏览器上有如下输出:

图片图片

默认开启 live Reload ,当代码发生改变时会自动重新对代码进行编译。

WDS 原理

webpack-dev-server 启动了一个使用 express 的HTTP服务,这个服务器与客户端采用 WebSocket 通信协议,当原始文件发生改变,webpack-dev-server 会实时编译,但是对 index.html 的修改不会做出处理。

通过查看 webpack-dev-server 源码中的 package.json 文件,我们发现这里定义了一个 bin 字段,那么这个 bin 有什么用呢?

bin 字段是命令名到本地文件名的映射。当我们使用 npm 或者 yarn 命令安装包时,如果该包的 package.json 文件有 bin 字段,就会在 node_modules 文件夹下面的 .bin 目录中复制了 bin 字段链接的执行文件。我们在调用执行文件时,可以不带路径,直接使用命令名来执行相对应的执行文件。

也就是说,当我们在终端中输入 npm install webpack-dev-server -D 的时候,会在 node_modules/.bin 目录下生成了三个文件:

图片图片

这三个文件中,其中的 ·cmd 是 windows 中默认的可执行文件,当我们不添加后缀名时,自动根据 pathext 查找文件。

当我们执行 npm start 的时候,会在 node_modules/.bin 目录下找到 webpack-dev-server.cmd 目录,因为这个文件是 windows 的批处理脚本:

@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0

IF EXIST "%dp0%node.exe" (
  SET "_prog=%dp0%node.exe"
) ELSE (
  SET "_prog=node"
  SET PATHEXT=%PATHEXT:;.JS;=;%
)

endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%"  "%dp0%..webpack-dev-serverbinwebpack-dev-server.js" %*

在这写代码里的最后一行 "%dp0%..webpack-dev-serverbinwebpack-dev-server.js" 命令中,通过软连接链接到 node_modules/webpack-dev-server/bin 中的 webpack-dev-server.js 目录。

所以当我们运行 npm start 的时候,也就是相当于运行 node_modules/.bin/webpack-dev-server.cmd serve 命令,并最终以 webpack-dev-server/bin/webpack-dev-server.js 就是整个命令行的入口,通过这里,它会自动给你开启 webpack-cli,详情请看下图:

图片图片

所以当我们没有安装 webpack-cli 的时候运行 npm start 时会有以下提示:

图片图片

在这里 webpack 就会基于我们 webpack.config.js 里创建一个 compiler,然后基于 compiler 和 devServer 相关配置生成一个 WebpackDevServer 实例,该实例会启动一个expores 服务来帮我们监听静态资源变化并更新。

在 webpack-dev-server 源代码中,有一个 Server.js 的目录,创建了一个 Server 类,用于启动的是 start(...) 方法中通过 socket 监听一个端口,默认使用的是 8080,初始化 client 和 dev-server,以 p[lugin 的形式挂载到 compiler 上,添加 hooks 插件,实例化 express 服务等等,有以下代码,详情可自行查看,可以通过安装依赖的方式,也可以到 GitHub:

图片图片

接下来我们看看 await this initialize(...) 都干了些啥?详情请看下列代码(省略了后面部分):

async initialize() {
    if (this.options.webSocketServer) {
      const compilers =
        /** @type {MultiCompiler} */
        (this.compiler).compilers || [this.compiler];

      compilers.forEach((compiler) => {
        this.addAdditionalEntries(compiler);
        const webpack = compiler.webpack || require("webpack");
        new webpack.ProvidePlugin({
          __webpack_dev_server_client__: this.getClientTransport(),
        }).apply(compiler);

        compiler.options.plugins = compiler.options.plugins || [];
        if (this.options.hot) {
          const HMRPluginExists = compiler.options.plugins.find(
            (p) => p.constructor === webpack.HotModuleReplacementPlugin
          );
          if (HMRPluginExists) {
            this.logger.warn(
              `"hot: true" automatically applies HMR plugin, you don't have to add it manually to your webpack configuration.`
            );
          } else {
            // Apply the HMR plugin
            const plugin = new webpack.HotModuleReplacementPlugin();
            plugin.apply(compiler);
          }
        }
      });

      if (
        this.options.client &&
        /** @type {ClientConfiguration} */ (this.options.client).progress
      ) {
        this.setupProgressPlugin();
      }
    }

在 initialize() 方法中还有以下方法的调用:

this.setupHooks();
this.setupApp();
this.setupHostHeaderCheck();
this.setupDevMiddleware();
this.setupBuiltInRoutes();
this.setupWatchFiles();
this.setupWatchStaticFiles();
this.setupMiddlewares();
this.createServer();

在这里,主要做的事情是将 client 以 plugin 的形式挂载到 compiler,如果存在 HMR,则开启 HMR,通过 this.setupWatchFiles() 监听文件变化。

在 this.setupHooks() 中,主要做的事情就是在 webpack 的 done 钩子上挂了个给客户端广播消息的回调,通过这个回调就知道工程代码有更新,这时候客户端就会发送请求给 express 服务去请求最新的 webpack 打包的代码,详情请看以下代码:

setupHooks() {
    this.compiler.hooks.invalid.tap("webpack-dev-server", () => {
      if (this.webSocketServer) {
        this.sendMessage(this.webSocketServer.clients, "invalid");
      }
    });
    this.compiler.hooks.done.tap(
      "webpack-dev-server",
      /**
       * @param {Stats | MultiStats} stats
       */
      (stats) => {
        if (this.webSocketServer) {
          this.sendStats(this.webSocketServer.clients, this.getStats(stats));
        }

        /**
         * @private
         * @type {Stats | MultiStats}
         */
        this.stats = stats;
      }
    );
  }

当客户端接收到 websocket 广播的消息后,会触发reloadApp方法(webpack打包时注入进去的)reloadApp会根据广播消息里的更新类型选择是页面更新 liveReload 还是模块更新 HMR,通过测试,发现每次修改正是都会经过 sendMessage 方法。

图片图片

上图正是 webpack-dev-server 的整个流程图,到这来,这篇文章的内容也就讲完了,如有错误,烦请批评指出。

本文转载于:https://juejin.cn/post/7187368174952644665

参考文献

  • # webpack-dev-server运行原理[1]
  • WDS源码[2]

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论