How Hot Reloading with Webpack and Phoenix

7 minute read

Live Reloading 和 Hot Reloading 的区别

Live Reloading是在浏览器进行更改后自动重新加载整个页面,使开发者不再需要切换浏览器手动点击 cmd + r 。在这点上 Phoenix 通过 Phoenix LiveReload 可以提供开箱即用的支持,通过 mix phx.new 创建的项目默认配置了 Phoenix LiveReload。

但是,Live Reloading依然存在一个缺点,在开发现在越来越高度交互的 Web 应用时,频繁的代码改动与调试,意味着频繁的重新加载整个页面,很多情况你也许只希望修改一些样式,Live Reloading会显得尤为耗时,而为了解决这个问题,Hot Reloading 显得尤为重要。

注意事项

本文通过 React + Webpack 进行配置举例(因为自身业务),以前 React 的 Hot Reloading 是通过 HMR (Hot Module Replacement) 实现的,因为一些技术原因不再使用这种方法。

我们将使用 @pmmmwh/react-refresh-webpack-plugin 这个实验性的 Webpack 插件来实现,虽然这个插件在运行一段时间后,webpack-dev-server 最终会吃掉大量内存,但是依然可以通过合适的配置和手动刷新,使其成为一个强大的工具。

NPM

首先在你的前端项目中执行,如果你是用的是 yarn 请自行修改命令

npm install --save-dev @pmmmwh/

react-refresh-webpack-plugin

webpack-dev-server react-refresh

react-refresh-typescript

Webpack 配置

在逻辑结构上我们将把 webpack-dev-server 置于 Phoenix 应用之前。使 Webpack 能够将 websocket 注入页面,以便在需要进行 Hot Reloading 时通知浏览器。为了更好地理解,我们配置 webpack-dev-server 启动在 4000 端口并且代理所有的请求到 4001 端口,也就是我们 Phoenix 应用运行的端口。所以现在浏览器仍然像 http://localhost:4000 发出请求,但是将会被透明代理到 Phoenix。

以下是 Webpack 的配置实例

assets/webpack.config.js

 1const ReactRefreshPlugin = require("@pmmmwh/react-refresh-webpack-plugin");
 2const ReactRefreshTypeScript = require("react-refresh-typescript");
 3const PHOENIX_TARGET = "http://localhost:4001";
 4
 5module.exports = (env, options) => {
 6
 7const isDevelopment = options.mode !== "production";
 8
 9return {
10	mode: options.mode,
11	// We use `webpack serve` for hot reloading with phoenix
12	// Phoenix serves to 4001, and webpack serves to 4000
13	// Developers should use 4000 for hot reload
14	devServer: {
15	proxy: {
16		// All requests that webpack doesn't handle
17		// go through to the Phoenix server
18	"*": {
19		target: PHOENIX_TARGET,
20		secure: false,
21		// We don't specify ws: true here because
22		// webpack serve --hot requires
23		// its own non-phoenix websocket
24		// (ws://localhost:4000/ws) for reloading modules
25	},
26	// WS: Live reload the entire website for development
27	"/phoenix/live_reload/socket/websocket": {
28		target: PHOENIX_TARGET,
29		secure: false,
30		ws: true,
31	},
32	// WS: Live view
33	"/live/websocket": {
34		target: PHOENIX_TARGET,
35		secure: false,
36		ws: true,
37	},
38	// WS: For our data and presence
39	"/socket/websocket": {
40		target: PHOENIX_TARGET,
41		secure: false,
42		ws: true,
43		},
44	},
45	devMiddleware: {
46		// webpack serve copies files into memory
47		// but we need it on disk to serve from
48		// proxied Phoenix's :4001 server
49		writeToDisk: true,
50		},
51		},
52		... rest of webpack config
53	};
54};

如上所示,列出了三种不同的 websocket,第一个用于 Phoenix LiveReload,第二个用于 Phoenix LiveView,第三个用于 Phoenix Channels。你也许已经自定义了这些配置的具体说明,可以在 MyAppWeb.Endpoint 中查找关键字 socket/3

loader 中找到 ts-loader(假设你已经在用 Typescript )并添加 getCustomTransformerstranspileOnly(注意 isDevelopment 变量在前面的代码中已经声明):

 1{
 2	loader: "ts-loader",
 3	options: {
 4		getCustomTransformers: () => ({
 5			before: [isDevelopment && ReactRefreshTypeScript()].filter(Boolean),
 6		}),
 7		transpileOnly: isDevelopment,
 8		... other ts-loader options
 9	},
10},

然后你需要在 plugins 中添加 ReactRefreshPlugin

1plugins: [
2	isDevelopment && new ReactRefreshPlugin(),
3	... other plugins
4].filter(Boolean)

硬盘写入

Webpack 通过 webpack-dev-server 构建的所有内容,只会保存在内存中,如此一来 Phoenix 无法渲染我们的静态文件。

在默认的 Phoenix 配置中中,我们使用 Webpack  [CopyPlugin](https://webpack.js.org/plugins/copy-webpack-plugin/#root)将静态文件从 assets/static 复制到 priv/static

但是使用 webpack-dev-server 的情况下,所有内容都是从内存中提供的(并且它不提供静态文件),所以我们需要启用 writeToDisk 选项来实际写出静态文件,以便 Phoenix 可以为它们提供服务。这就是为什么我们必须在 webpack-dev-server Webpack 配置片段中添加 writeToDisk: true

Phoenix 配置实例

 1config :my_app, MyAppWeb.Endpoint,
 2http: [port: 4001],
 3url: [port: 4000],
 4# also change your watchers
 5# configuration to run webpack serve:
 6watchers: [
 7	node: [
 8		"node_modules/webpack/bin/webpack.js",
 9		# this indicates to webpack
10		# to run the webpack-dev-server
11		"serve",
12		"--watch-options-stdin",
13		"--port",
14		# webpack serves to 4000 to enable hot reloading,
15		# but proxies to 4001 for phoenix
16		# to handle the request
17		"4000",
18		"--mode",
19		"development",
20		cd: Path.expand("../assets", __DIR__)
21	]
22],
23... other configuration

dev.exs 中,将 http 端口更改为 4001,然后将 url 设置为 4000。这将使 Phoenix 绑定到端口 4001,它将实际侦听请求,但生成到端口 4000 的链接,如此一来 webpack-dev-server 便能正常工作。