diff options
author | Kristóf Marussy <kristof@marussy.com> | 2021-12-30 00:26:01 +0100 |
---|---|---|
committer | Kristóf Marussy <kristof@marussy.com> | 2021-12-30 02:24:28 +0100 |
commit | 61fd13c55f5e69a9d8b32dd0d74b08870783bcce (patch) | |
tree | 4f3f97b1629f3c262bea076b596bc7245ccbc0bd /scripts/watch.js | |
parent | Revert "refactor: Switch back to consola for prettyness" (diff) | |
download | sophie-61fd13c55f5e69a9d8b32dd0d74b08870783bcce.tar.gz sophie-61fd13c55f5e69a9d8b32dd0d74b08870783bcce.tar.zst sophie-61fd13c55f5e69a9d8b32dd0d74b08870783bcce.zip |
build: Switch to esbuild
We will build all packages except the frontend (where vite remains in
use) with esbuild.
For some reason, the @yarnpkg/esbuild-plugin-pnp doesn't allow esbuild
to load esm modules and we fall back to commonjs for dependencies.
Hence we had to switch back to node_modules (but still rely on yarn
hardlinking for a more efficient install).
Diffstat (limited to 'scripts/watch.js')
-rw-r--r-- | scripts/watch.js | 259 |
1 files changed, 129 insertions, 130 deletions
diff --git a/scripts/watch.js b/scripts/watch.js index 82dc01e..367ab88 100644 --- a/scripts/watch.js +++ b/scripts/watch.js | |||
@@ -1,32 +1,23 @@ | |||
1 | #!/usr/bin/env node | ||
2 | |||
3 | // @ts-check | 1 | // @ts-check |
4 | 2 | ||
3 | const esbuild = require('esbuild'); | ||
5 | const { spawn } = require('child_process'); | 4 | const { spawn } = require('child_process'); |
5 | const chokidar = require('chokidar'); | ||
6 | const electronPath = require('electron'); | 6 | const electronPath = require('electron'); |
7 | const { build, createLogger, createServer } = require('vite'); | 7 | const { join } = require('path'); |
8 | const { createServer } = require('vite'); | ||
8 | 9 | ||
9 | /** @type {string} */ | 10 | process.env.MODE = 'development'; |
10 | const mode = process.env.MODE = process.env.MODE || 'development'; | 11 | process.env.NODE_ENV = 'development'; |
11 | 12 | ||
12 | /** @type {import('vite').LogLevel} */ | 13 | /** @type {string} */ |
13 | const LOG_LEVEL = 'info'; | 14 | const sharedPackageSource = packageSource('shared'); |
14 | 15 | ||
15 | /** @type {import('vite').InlineConfig} */ | 16 | /** @type {string} */ |
16 | const sharedConfig = { | 17 | const serviceSharedPackageSource = packageSource('service-shared'); |
17 | mode, | ||
18 | build: { | ||
19 | watch: {}, | ||
20 | }, | ||
21 | logLevel: LOG_LEVEL, | ||
22 | }; | ||
23 | 18 | ||
24 | /** | 19 | /** @type {RegExp[]} */ |
25 | * Messages on stderr that match any of the contained patterns will be stripped from output | 20 | const stderrIgnorePatterns = [ |
26 | * | ||
27 | * @type {RegExp[]} | ||
28 | */ | ||
29 | const stderrFilterPatterns = [ | ||
30 | // warning about devtools extension | 21 | // warning about devtools extension |
31 | // https://github.com/cawa-93/vite-electron-builder/issues/492 | 22 | // https://github.com/cawa-93/vite-electron-builder/issues/492 |
32 | // https://github.com/MarshallOfSound/electron-devtools-installer/issues/143 | 23 | // https://github.com/MarshallOfSound/electron-devtools-installer/issues/143 |
@@ -37,27 +28,102 @@ const stderrFilterPatterns = [ | |||
37 | ]; | 28 | ]; |
38 | 29 | ||
39 | /** | 30 | /** |
40 | * @param {{name: string; configFile: string; writeBundle?: import('rollup').OutputPlugin['writeBundle'] }} config | 31 | * @param {string} packageName |
41 | * @returns {Promise<unknown>} | 32 | * @returns {string} |
33 | */ | ||
34 | function packageSource(packageName) { | ||
35 | return join(__dirname, `../packages/${packageName}/src`); | ||
36 | } | ||
37 | |||
38 | /** | ||
39 | * @param {string} packageName | ||
40 | * @param {string[]} [extraPaths] | ||
41 | * @param {() => void} [callback] | ||
42 | * @return {Promise<void>} | ||
43 | */ | ||
44 | async function setupEsbuildWatcher(packageName, extraPaths, callback) { | ||
45 | const config = require(`../packages/${packageName}/esbuild.config.js`); | ||
46 | config.logLevel = 'info'; | ||
47 | config.incremental = true; | ||
48 | const incrementalBuild = await esbuild.build(config); | ||
49 | const paths = [ | ||
50 | packageSource(packageName), | ||
51 | ...(extraPaths || []), | ||
52 | ]; | ||
53 | const watcher = chokidar.watch(paths, { | ||
54 | ignored: /(^|[\/\\])\.|__(tests|mocks)__|\.(spec|test)\.[jt]sx?$/, | ||
55 | ignoreInitial: true, | ||
56 | persistent: true, | ||
57 | }); | ||
58 | if (callback) { | ||
59 | callback(); | ||
60 | } | ||
61 | watcher.on('change', () => { | ||
62 | incrementalBuild.rebuild().then(() => { | ||
63 | console.log(`\u26a1 Reloading package ${packageName}`); | ||
64 | if (callback) { | ||
65 | callback(); | ||
66 | } | ||
67 | }).catch((err) => { | ||
68 | const errCount = err.errors.length; | ||
69 | console.error( | ||
70 | '\ud83d\udd25', | ||
71 | errCount, | ||
72 | errCount > 1 ? 'errors' : 'error', | ||
73 | 'while rebuilding package', | ||
74 | packageName | ||
75 | ); | ||
76 | }); | ||
77 | }); | ||
78 | } | ||
79 | |||
80 | /** | ||
81 | * @param {string} packageName | ||
82 | * @return {Promise<import('vite').ViteDevServer>} | ||
42 | */ | 83 | */ |
43 | function getWatcher({ name, configFile, writeBundle }) { | 84 | async function setupDevServer(packageName) { |
44 | return build({ | 85 | const viteDevServer = await createServer({ |
45 | ...sharedConfig, | 86 | build: { |
46 | configFile, | 87 | watch: { |
47 | plugins: [ | 88 | skipWrite: true, |
48 | { | 89 | clearScreen: false, |
49 | name, | ||
50 | writeBundle, | ||
51 | }, | 90 | }, |
52 | ], | 91 | }, |
92 | configFile: join(__dirname, `../packages/${packageName}/vite.config.js`), | ||
93 | }); | ||
94 | await viteDevServer.listen(); | ||
95 | return viteDevServer; | ||
96 | } | ||
97 | |||
98 | /** | ||
99 | * @param {(event: import('vite').HMRPayload) => void} sendEvent | ||
100 | * @return {Promise<void>} | ||
101 | */ | ||
102 | function setupPreloadPackageWatcher(sendEvent) { | ||
103 | return setupEsbuildWatcher('preload', [sharedPackageSource], () => { | ||
104 | sendEvent({ | ||
105 | type: 'full-reload', | ||
106 | }); | ||
107 | }); | ||
108 | } | ||
109 | |||
110 | /** | ||
111 | * @param {string} packageName | ||
112 | * @param {(event: import('vite').HMRPayload) => void} sendEvent | ||
113 | * @return {Promise<void>} | ||
114 | */ | ||
115 | function setupServicePackageWatcher(packageName, sendEvent) { | ||
116 | return setupEsbuildWatcher(packageName, [serviceSharedPackageSource], () => { | ||
117 | sendEvent({ | ||
118 | type: 'custom', | ||
119 | event: 'sophie:reload-services', | ||
120 | }); | ||
53 | }); | 121 | }); |
54 | } | 122 | } |
55 | 123 | ||
56 | /** | 124 | /** |
57 | * Start or restart App when source files are changed. | ||
58 | * | ||
59 | * @param {import('vite').ViteDevServer} viteDevServer | 125 | * @param {import('vite').ViteDevServer} viteDevServer |
60 | * @returns {Promise<unknown>} | 126 | * @return {Promise<void>} |
61 | */ | 127 | */ |
62 | function setupMainPackageWatcher(viteDevServer) { | 128 | function setupMainPackageWatcher(viteDevServer) { |
63 | // Write a value to an environment variable to pass it to the main process. | 129 | // Write a value to an environment variable to pass it to the main process. |
@@ -67,122 +133,55 @@ function setupMainPackageWatcher(viteDevServer) { | |||
67 | const path = '/'; | 133 | const path = '/'; |
68 | process.env.VITE_DEV_SERVER_URL = `${protocol}//${host}:${port}${path}`; | 134 | process.env.VITE_DEV_SERVER_URL = `${protocol}//${host}:${port}${path}`; |
69 | 135 | ||
70 | const logger = createLogger( | ||
71 | LOG_LEVEL, | ||
72 | { | ||
73 | prefix: '[main]', | ||
74 | }, | ||
75 | ); | ||
76 | |||
77 | /** @type {import('child_process').ChildProcessWithoutNullStreams | null} */ | 136 | /** @type {import('child_process').ChildProcessWithoutNullStreams | null} */ |
78 | let spawnProcess = null; | 137 | let spawnProcess = null; |
79 | 138 | ||
80 | return getWatcher({ | 139 | return setupEsbuildWatcher( |
81 | name: 'reload-app-on-main-package-change', | 140 | 'main', |
82 | configFile: 'packages/main/vite.config.js', | 141 | [ |
83 | writeBundle() { | 142 | serviceSharedPackageSource, |
143 | sharedPackageSource | ||
144 | ], | ||
145 | () => { | ||
84 | if (spawnProcess !== null) { | 146 | if (spawnProcess !== null) { |
85 | spawnProcess.kill('SIGINT'); | 147 | spawnProcess.kill('SIGINT'); |
86 | spawnProcess = null; | 148 | spawnProcess = null; |
87 | } | 149 | } |
88 | 150 | ||
89 | spawnProcess = spawn(String(electronPath), ['.']); | 151 | spawnProcess = spawn(String(electronPath), ['.'], { |
90 | 152 | stdio: ['inherit', 'inherit', 'pipe'], | |
91 | spawnProcess.stdout.on('data', (data) => { | ||
92 | if (data.toString().trim() !== '') { | ||
93 | logger.warn(data.toString(), {timestamp: true}) | ||
94 | } | ||
95 | }); | 153 | }); |
96 | 154 | ||
97 | spawnProcess.stderr.on('data', (data) => { | 155 | spawnProcess.stderr.on('data', (data) => { |
98 | const trimmedData = data.toString().trim(); | 156 | const stderrString = data.toString('utf-8').trimRight(); |
99 | if (trimmedData === '') { | 157 | if (!stderrIgnorePatterns.some((r) => r.test(stderrString))) { |
100 | return; | 158 | console.error(stderrString); |
101 | } | 159 | } |
102 | const mayIgnore = stderrFilterPatterns.some((r) => r.test(data)); | ||
103 | if (mayIgnore) { | ||
104 | return; | ||
105 | } | ||
106 | logger.error(data, { timestamp: true }); | ||
107 | }); | ||
108 | }, | ||
109 | }); | ||
110 | } | ||
111 | |||
112 | /** | ||
113 | * Reload App when source files are changed. | ||
114 | * | ||
115 | * @param {import('vite').ViteDevServer} viteDevServer | ||
116 | * @returns {Promise<unknown>} | ||
117 | */ | ||
118 | function setupPreloadPackageWatcher(viteDevServer) { | ||
119 | return getWatcher({ | ||
120 | name: 'reload-page-on-preload-package-change', | ||
121 | configFile: 'packages/preload/vite.config.js', | ||
122 | writeBundle() { | ||
123 | viteDevServer.ws.send({ | ||
124 | type: 'full-reload', | ||
125 | }); | ||
126 | }, | ||
127 | }); | ||
128 | } | ||
129 | |||
130 | /** | ||
131 | * Reload services when source files are changed. | ||
132 | * | ||
133 | * @param {import('vite').ViteDevServer} viteDevServer | ||
134 | * @param {string} packageName The name of the package to watch. | ||
135 | * @returns {Promise<unknown>} | ||
136 | */ | ||
137 | function setupServicePreloadPackageWatcher(viteDevServer, packageName) { | ||
138 | return getWatcher({ | ||
139 | name: `reload-services-on-${packageName}-package-change`, | ||
140 | configFile: `packages/${packageName}/vite.config.js`, | ||
141 | writeBundle() { | ||
142 | console.log('wrote'); | ||
143 | viteDevServer.ws.send({ | ||
144 | type: 'custom', | ||
145 | event: 'sophie:reload-services', | ||
146 | }); | 160 | }); |
147 | }, | 161 | }, |
148 | }); | 162 | ); |
149 | } | ||
150 | |||
151 | /** | ||
152 | * Rebuild package when source files are changed. | ||
153 | * | ||
154 | * @param {string} packageName The name of the package to watch. | ||
155 | * @returns {Promise<unknown>} | ||
156 | */ | ||
157 | function setupSharedPackageWatcher(packageName) { | ||
158 | return getWatcher({ | ||
159 | name: `rebuild-package-on-${packageName}-package-change`, | ||
160 | configFile: `packages/${packageName}/vite.config.js`, | ||
161 | }); | ||
162 | } | 163 | } |
163 | 164 | ||
164 | /** | 165 | /** |
165 | * @returns {Promise<unknown>} | 166 | * @returns {Promise<void>} |
166 | */ | 167 | */ |
167 | async function setupDevEnvironment() { | 168 | async function setupDevEnvironment() { |
168 | const serviceSharedWatcher = setupSharedPackageWatcher('service-shared'); | 169 | /** @type {import('vite').ViteDevServer | null} */ |
169 | 170 | let viteDevServer = null; | |
170 | await setupSharedPackageWatcher('shared'); | 171 | /** @type {(event: import('vite').HMRPayload) => void} */ |
171 | 172 | const sendEvent = (event) => { | |
172 | const viteDevServer = await createServer({ | 173 | if (viteDevServer !== null) { |
173 | ...sharedConfig, | 174 | viteDevServer.ws.send(event); |
174 | configFile: 'packages/renderer/vite.config.js', | 175 | } |
175 | }); | 176 | }; |
176 | await viteDevServer.listen(); | ||
177 | |||
178 | await Promise.all([ | 177 | await Promise.all([ |
179 | setupPreloadPackageWatcher(viteDevServer), | 178 | setupEsbuildWatcher('shared'), |
180 | serviceSharedWatcher.then(() => Promise.all([ | 179 | setupPreloadPackageWatcher(sendEvent), |
181 | setupServicePreloadPackageWatcher(viteDevServer, 'service-inject'), | 180 | setupServicePackageWatcher('service-inject', sendEvent), |
182 | setupServicePreloadPackageWatcher(viteDevServer, 'service-preload'), | 181 | setupServicePackageWatcher('service-preload', sendEvent), |
183 | ])), | ||
184 | ]); | 182 | ]); |
185 | 183 | viteDevServer = await setupDevServer('renderer'); | |
184 | console.log('\ud83c\udf80 Sophie is starting up') | ||
186 | return setupMainPackageWatcher(viteDevServer); | 185 | return setupMainPackageWatcher(viteDevServer); |
187 | } | 186 | } |
188 | 187 | ||