isolate-package 1.3.3 → 1.4.1-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,21 +1,24 @@
1
1
  # Isolate Package
2
2
 
3
- Isolate a monorepo workspace package so that it can be deployed as a completely
4
- self-contained directory with the sources of all its local dependencies
5
- included.
3
+ Isolate a monorepo workspace package to form a self-contained deployable package
4
+ that includes internal dependencies and a compatible lockfile.
6
5
 
7
6
  <!-- TOC -->
8
7
 
9
- - [Motivation](#motivation)
10
8
  - [Features](#features)
11
- - [Firebase Deployment Quickstart](#firebase-deployment-quickstart)
9
+ - [Motivation](#motivation)
10
+ - [Install](#install)
11
+ - [Usage as binary](#usage-as-binary)
12
+ - [Usage as function](#usage-as-function)
12
13
  - [Prerequisites](#prerequisites)
13
- - [Define shared package dependencies in the manifest](#define-shared-package-dependencies-in-the-manifest)
14
- - [Define "files" and "version" in each manifest](#define-files-and-version-in-each-manifest)
14
+ - [Define shared dependencies in the package manifest](#define-shared-dependencies-in-the-package-manifest)
15
+ - [Define "version" field in each package manifest](#define-version-field-in-each-package-manifest)
16
+ - [Define "files" field in each package manifest](#define-files-field-in-each-package-manifest)
15
17
  - [Use a flat structure inside your packages folders](#use-a-flat-structure-inside-your-packages-folders)
16
- - [Usage](#usage)
17
- - [Deploying to Firebase](#deploying-to-firebase)
18
- - [Deploying to Firebase from the root](#deploying-to-firebase-from-the-root)
18
+ - [Working with Firebase](#working-with-firebase)
19
+ - [A Quick Start](#a-quick-start)
20
+ - [Deploying from multiple packages](#deploying-from-multiple-packages)
21
+ - [Deploying from the root](#deploying-from-the-root)
19
22
  - [Configuration Options](#configuration-options)
20
23
  - [buildDirName](#builddirname)
21
24
  - [excludeLockfile](#excludelockfile)
@@ -28,77 +31,96 @@ included.
28
31
  - [workspaceRoot](#workspaceroot)
29
32
  - [Troubleshooting](#troubleshooting)
30
33
  - [Lockfiles](#lockfiles)
34
+ - [PNPM](#pnpm)
31
35
  - [NPM](#npm)
32
- - [PNPM Lockfiles disabled for now](#pnpm-lockfiles-disabled-for-now)
36
+ - [Yarn](#yarn)
37
+ - [A Partial Workaround](#a-partial-workaround)
33
38
  - [Different Package Managers](#different-package-managers)
34
- - [Yarn v1 and v3](#yarn-v1-and-v3)
35
39
  - [Using the Firebase Functions Emulator](#using-the-firebase-functions-emulator)
40
+ - [The internal packages strategy](#the-internal-packages-strategy)
36
41
 
37
42
  <!-- /TOC -->
38
43
 
44
+ ## Features
45
+
46
+ - Isolate a monorepo package with its internal dependencies to form a
47
+ self-contained installable package.
48
+ - Deterministic deployment by generating an isolated lockfile based on the
49
+ existing monorepo lockfile. Currently this feature is only supported for PNPM.
50
+ See [lockfiles](#lockfiles) for more information.
51
+ - Zero-config for the vast majority of use-cases, with no manual steps involved.
52
+ - Support for PNPM, NPM and Yarn.
53
+ - Compatible with the Firebase tools CLI, incl 1st gen and 2nd gen Firebase
54
+ functions deployments.
55
+ - Uses a pack/unpack approach to isolate only those files that would have been
56
+ part of a published NPM package.
57
+ - Isolates internal workspace dependencies recursively. If package A depends on
58
+ internal package B which depends on internal package C, all of them will be
59
+ included.
60
+ - Optionally include devDependencies in the isolated output.
61
+
39
62
  ## Motivation
40
63
 
41
- This solution was developed out of a desire to deploy to
42
- [Firebase](https://firebase.google.com/) from a monorepo without resorting to
43
- hacks, shell scripts and manual tasks. I have written an article explaining the
44
- issue [here](https://medium.com/p/e685de39025e).
64
+ This solution was born from a desire to deploy to
65
+ [Firebase](https://firebase.google.com/) from a monorepo without requiring
66
+ custom shell scripts and other hacks. Here is
67
+ [an article](https://thijs-koerselman.medium.com/deploy-to-firebase-without-the-hacks-e685de39025e)
68
+ explaining the issue in more detail.
45
69
 
46
- There is nothing Firebase specific to this solution but I am currently not aware
47
- of other reasons to isolate a workspace package. If you find a different
48
- use-case, I would love to hear about it.
70
+ There is nothing Firebase-specific to this solution and there should be other
71
+ use-cases for it, but that is why this documentation contains some instructions
72
+ related to Firebase.
49
73
 
50
- In the documentation and code you will see the word "manifest" a lot, and it
51
- simply means to the contents of a `package.json` file.
74
+ ## Install
52
75
 
53
- ## Features
76
+ Run `pnpm install isolate-package --dev` or the equivalent for `yarn` or `npm`.
54
77
 
55
- - Zero-config for the vast majority of use-cases, with no manual steps involved.
56
- - Support NPM, Yarn classic (v1) and current (v3) and PNPM.
57
- - Fully compatible with the Firebase tools CLI, supporting 1st gen and 2nd gen
58
- Firebase functions.
59
- - Uses a pack/unpack approach to isolate only the files that would have been
60
- part of a published package, so the output contains a minimal set of files.
61
- - Isolates shared dependencies recursively. If package A depends on local
62
- package B which depends on local package C, all of them will be isolated.
63
- - Includes the lockfile so the isolated deployment should be deterministic. PNPM
64
- lockfiles are not supported yet. See [lockfiles](#lockfiles) for more info.
65
- - Optionally include devDependencies in the isolated output.
78
+ I recommend using `pnpm` for
79
+ [a number of reasons](https://pnpm.io/feature-comparison). Also, at the time of
80
+ writing it is the only package manager for which isolate-package can generate a
81
+ lockfile. For more information see [lockfiles](#lockfiles).
66
82
 
67
- ## Firebase Deployment Quickstart
83
+ ## Usage as binary
68
84
 
69
- This describes the steps required for Firebase deployment, assuming:
85
+ This package exposes a binary called `isolate`.
70
86
 
71
- - You use a fairly typical monorepo setup
72
- - Your `firebase.json` config lives in the root of the package that you like to
73
- deploy to Firebase, hereafter referred to as the "target package".
87
+ Run `npx isolate` from the root of the package you want to isolate. Make sure
88
+ you build the package first.
74
89
 
75
- If you use a different setup, just continue reading the
76
- [Prerequisites](#prerequisites) section.
90
+ The `isolate` binary will try to infer your build output location from a
91
+ `tsconfig` file, but see the [buildDirName configuration](#builddirname) if you
92
+ are not using Typescript.
77
93
 
78
- 1. In the target package, install isolate-package and firebase-tools by running
79
- `pnpm add isolate-package firebase-tools -D` or the Yarn / NPM equivalent. I
80
- like to install firebase-tools as a devDependency in every firebase package,
81
- but you could of course also use a global install if you prefer.
82
- 2. In the `firebase.json` config set `"source"` to `"./isolate"` and
83
- `"predeploy"` to `["turbo build", "isolate"]` or whatever suits your build
84
- tool.
85
- 3. From the target package root, you should now be able to deploy with `npx
86
- firebase deploy` or `npx firebase deploy --only functions` in case your package
87
- only contains code for Firebase functions.
94
+ By default the isolated output will become available at `./isolate`.
88
95
 
89
- I recommend keeping a `firebase.json` file inside each Firebase package (as
90
- opposed to the monorepo root), because it allows you to deploy from multiple
91
- independent packages. This give you more flexibility to organize your code. It
92
- also makes it easy to deploy 1st gen functions next to 2nd gen functions, or mix
93
- different node versions should you want to. Your bundle sizes and dependency
94
- lists for each function might also decrease, which improves cold-start times.
96
+ If you are here to simplify and improve your Firebase deployments check out the
97
+ [Firebase quick start guide](#a-quick-start).
98
+
99
+ ## Usage as function
100
+
101
+ Alternatively, `isolate` can be integrated in other programs by importing it as
102
+ a function. You optionally pass it a some user configuration and possibly a
103
+ logger to handle any output messages should you need to write them to a
104
+ different location as the standard `node:console`.
105
+
106
+ ```ts
107
+ import { isolate } from "isolate-package";
108
+
109
+ await isolate({
110
+ config: { logLevel: "debug" },
111
+ logger: customLogger,
112
+ });
113
+ ```
114
+
115
+ If you do not pass in any configuration, the function will try to read a
116
+ `isolate.config.json` file from disk. You can set
95
117
 
96
118
  ## Prerequisites
97
119
 
98
120
  Because historically many different approaches to monorepos exist, we need to
99
121
  establish some basic rules for the isolate process to work.
100
122
 
101
- ### Define shared package dependencies in the manifest
123
+ ### Define shared dependencies in the package manifest
102
124
 
103
125
  This one might sound obvious, but if the `package.json` from the package you are
104
126
  targeting does not list the other monorepo packages it depends on, in either the
@@ -124,14 +146,23 @@ work (some depending on your package manager):
124
146
  So if the a package name can be found as part of the workspace definition, it
125
147
  will be processed regardless of its version specifier.
126
148
 
127
- ### Define "files" and "version" in each manifest
149
+ ### Define "version" field in each package manifest
150
+
151
+ The `version` field is required for `pack` to execute, because it is use to
152
+ generate part of the packed filename. A personal preference is to set it to
153
+ `"0.0.0"` to indicate that the version does not have any real meaning.
154
+
155
+ ### Define "files" field in each package manifest
156
+
157
+ > NOTE: This step is not required if you use the
158
+ > [internal packages strategy](#the-internal-packages-strategy)
128
159
 
129
160
  The isolate process uses (p)npm `pack` to extract files from package
130
161
  directories, just like publishing a package would.
131
162
 
132
163
  For this to work it is required that you define the `files` property in each
133
- `package.json` manifest, as it declares what files should be included in the
134
- published output.
164
+ package manifest, as it declares what files should be included in the published
165
+ output.
135
166
 
136
167
  Typically the value contains an array with just the name of the build output
137
168
  directory, for example:
@@ -143,15 +174,11 @@ directory, for example:
143
174
  }
144
175
  ```
145
176
 
146
- The `version` field is also required for `pack` to execute. I personally always
147
- set it to `"0.0.0"` to indicate that the version does not have a practical
148
- function.
149
-
150
177
  A few additional files will be included by `pack` automatically, like the
151
178
  `package.json` and `README.md` files.
152
179
 
153
- **Tip** If you deploy to Firebase [2nd
154
- generation](https://firebase.google.com/docs/firestore/extend-with-functions-2nd-gen)
180
+ **Tip** If you deploy to Firebase
181
+ [2nd generation](https://firebase.google.com/docs/firestore/extend-with-functions-2nd-gen)
155
182
  functions, you might want to include some .env files in the "files" list, so
156
183
  they are packaged and deployed together with your build output (as 1st gen
157
184
  functions config is no longer supported).
@@ -160,32 +187,58 @@ functions config is no longer supported).
160
187
 
161
188
  At the moment, nesting packages inside packages is not supported.
162
189
 
163
- When building the registry of all local packages, `isolate` doesn't drill down
164
- into the folders. So if you declare your packages to live in `packages/*` it
165
- will only find the packages directly in that folder and not at
190
+ When building the registry of all internal packages, `isolate` doesn't drill
191
+ down into the folders. So if you declare your packages to live in `packages/*`
192
+ it will only find the packages directly in that folder and not at
166
193
  `packages/nested/more-packages`.
167
194
 
168
- You can, however, declare multiple packages folders. I personally like to use
169
- `["packages/*", "apps/*", "services/*"]`. It's just that the structure inside
170
- them should be flat.
195
+ You can, however, declare multiple workspace packages directories. Personally, I
196
+ prefer to use `["packages/*", "apps/*", "services/*"]`. It is only the structure
197
+ inside them that should be flat.
171
198
 
172
- ## Usage
199
+ ## Working with Firebase
173
200
 
174
- Run `npm install isolate-package --dev` or the equivalent for `yarn` or `pnpm`.
201
+ ### A Quick Start
175
202
 
176
- This package exposes the `isolate` executable. Once installed you can run `npx
177
- isolate` in any package directory _after_ you have build the source files. By
178
- default this will produce a directory at `./isolate` but this can be configured.
203
+ If you are not confident that your monorepo setup is solid, please check out my
204
+ in-dept example at [mono-ts](https://github.com/0x80/mono-ts) where many
205
+ different aspects are discussed and `isolate-package` is used to demonstrate
206
+ Firebase deployments.
179
207
 
180
- You will probably want to add the output directory to your `.gitignore` file.
208
+ This section describes the steps required for Firebase deployment, assuming:
181
209
 
182
- ### Deploying to Firebase
210
+ - You use a fairly typical monorepo setup
211
+ - Your `firebase.json` config lives in the root of the package that you like to
212
+ deploy to Firebase, hereafter referred to as the "target package".
213
+
214
+ If your setup diverges from a traditional one, please continue reading the
215
+ [Prerequisites](#prerequisites) section.
183
216
 
184
- You can deploy to Firebase from multiple packages in your monorepo, so I advise
185
- you to co-locate your `firebase.json` file with the source code, and not place
186
- it in the root of the monorepo. If you do want to keep the firebase config in
187
- the root, read the instructions for [deploying to Firebase from the
188
- root](#deploying-to-firebase-from-the-root).
217
+ 1. In the target package, install `isolate-package` and `firebase-tools` by
218
+ running `pnpm add isolate-package firebase-tools -D` or the Yarn / NPM
219
+ equivalent. I tend to install firebase-tools as a devDependency in every
220
+ Firebase package, but you could also use a global install if you prefer that.
221
+ 2. In the `firebase.json` config set `"source"` to `"./isolate"` and
222
+ `"predeploy"` to `["turbo build", "isolate"]` or whatever suits your build
223
+ tool. The important part here is that isolate is being executed after the
224
+ build stage.
225
+ 3. From the target package folder, you should now be able to deploy with
226
+ `npx firebase deploy`.
227
+
228
+ I recommend keeping a `firebase.json` file inside each Firebase package (as
229
+ opposed to the monorepo root), because it allows you to deploy from multiple
230
+ independent packages. It makes it easy to deploy 1st gen functions next to 2nd
231
+ gen functions, deploy different node versions, and decrease the built output
232
+ size and dependency lists for each package, improving deployment and cold-start
233
+ times.
234
+
235
+ ### Deploying from multiple packages
236
+
237
+ You can deploy to Firebase from multiple packages in your monorepo, in which
238
+ case you co-locate your `firebase.json` file with the source code, and not in
239
+ the root of the monorepo. If you do want to keep the firebase config in the
240
+ root, read the instructions for
241
+ [deploying to Firebase from the root](#deploying-to-firebase-from-the-root).
189
242
 
190
243
  In order to deploy to Firebase, the `functions.source` setting in
191
244
  `firebase.json` needs to point to the isolated output folder, which would be
@@ -210,15 +263,15 @@ from the package.
210
263
 
211
264
  If you like to deploy to Firebase Functions from multiple packages you will also
212
265
  need to configure a unique `codebase` identifier for each of them. For more
213
- information, [read
214
- this](https://firebase.google.com/docs/functions/beta/organize-functions).
266
+ information,
267
+ [read this](https://firebase.google.com/docs/functions/beta/organize-functions).
215
268
 
216
269
  Make sure your Firebase package adheres to the things mentioned in
217
- [prerequisites](#prerequisites) and its manifest file contains the field
270
+ [prerequisites](#prerequisites) and its package manifest contains the field
218
271
  `"main"`, or `"module"` if you set `"type": "module"`, so Firebase knows the
219
272
  entry point to your source code.
220
273
 
221
- ### Deploying to Firebase from the root
274
+ ### Deploying from the root
222
275
 
223
276
  If, for some reason, you choose to keep the `firebase.json` file in the root of
224
277
  the monorepo you will have to place a configuration file called
@@ -248,8 +301,8 @@ The Firebase configuration should then look something like this:
248
301
  For most users no configuration should be necessary.
249
302
 
250
303
  You can configure the isolate process by placing a `isolate.config.json` file in
251
- the package that you want to isolate, except when you're [deploying to Firebase
252
- from the root of the workspace](#deploying-firebase-from-the-root).
304
+ the package that you want to isolate, except when you're
305
+ [deploying to Firebase from the root of the workspace](#deploying-firebase-from-the-root).
253
306
 
254
307
  For the config file to be picked up, you will have to execute `isolate` from the
255
308
  same location, as it uses the current working directory.
@@ -268,15 +321,14 @@ setting to specify where the build output files are located.
268
321
 
269
322
  Type: `boolean`, default: Depends on package manager.
270
323
 
271
- Sets the inclusion or exclusion of the lockfile as part of the deployment. For
272
- Yarn and NPM the lockfiles are included by default, but for PNPM they are
273
- excluded by default because they are not supported yet. For more information see
274
- [lockfiles](#lockfiles).
324
+ Sets the inclusion or exclusion of the lockfile as part of the deployment.
325
+
326
+ PNPM lockfiles are regenerated based on the isolated output, so they are
327
+ included by default.
275
328
 
276
- _Tip:_ If you can't use a lockfile I advise you to declare dependencies using
277
- absolute versions in your manifest files. This doesn't prevent their
278
- dependencies from installing newer versions, but at least you minimize the risk
279
- of things breaking.
329
+ For NPM and Yarn the lockfiles are excluded by default because they are
330
+ currently copied as-is to the isolate output and can lead to issues during
331
+ deployment installs. For more information see [lockfiles](#lockfiles).
280
332
 
281
333
  ### includeDevDependencies
282
334
 
@@ -298,7 +350,7 @@ Type: `"info" | "debug" | "warn" | "error"`, default: `"info"`.
298
350
 
299
351
  Because the configuration loader depends on this setting, its output is not
300
352
  affected by this setting. If you want to debug the configuration set
301
- `ISOLATE_CONFIG_LOG_LEVEL=debug` before you run `isolate`
353
+ `DEBUG_ISOLATE_CONFIG=true` before you run `isolate`
302
354
 
303
355
  ### targetPackagePath
304
356
 
@@ -344,84 +396,108 @@ want to isolate is located 2 levels up from the root.
344
396
  For example
345
397
 
346
398
  ```
399
+ packages
400
+ ├─ backend
401
+ │ └─ package.json
402
+ └─ ui
403
+ └─ package.json
347
404
  apps
348
- ├─ api
349
- ├─ package.json
350
- │ └─ .eslintrc.js
405
+ ├─ admin
406
+ └─ package.json
351
407
  └─ web
352
- ├─ package.json
353
- └─ .eslintrc.js
354
- packages
355
- └─ eslint-config-custom
356
- ├─ index.js
357
408
  └─ package.json
409
+ services
410
+ └─ api
411
+ └─ package.json
412
+
358
413
  ```
359
414
 
360
415
  When you use the `targetPackagePath` option, this setting will be ignored.
361
416
 
362
417
  ## Troubleshooting
363
418
 
364
- If something is not working, I advise you to add a `isolate.config.json` file,
365
- and set `"logLevel"` to `"debug"`. This should give you detailed feedback in the
419
+ If something is not working as expected, add a `isolate.config.json` file, and
420
+ set `"logLevel"` to `"debug"`. This should give you detailed feedback in the
366
421
  console.
367
422
 
368
423
  In addition define an environment variable to debug the configuration being used
369
- by setting `ISOLATE_CONFIG_LOG_LEVEL=debug` before you execute `isolate`
424
+ by setting `DEBUG_ISOLATE_CONFIG=true` before you execute `isolate`.
370
425
 
371
426
  When debugging Firebase deployment issues it might be convenient to trigger the
372
427
  isolate process manually with `npx isolate` and possibly
373
- `ISOLATE_CONFIG_LOG_LEVEL=debug npx isolate`
428
+ `DEBUG_ISOLATE_CONFIG=true npx isolate`
374
429
 
375
430
  ## Lockfiles
376
431
 
377
- The lockfiles for NPM as well as the Yarn v1 and v3 seem to have a flat
378
- structure unrelated to the workspace packages structure, so they are copied to
379
- the isolate output as-is.
432
+ Deploying the isolated code together with a valid lockfile turned out to be the
433
+ biggest challenge of this solution.
434
+
435
+ A lockfile in a monorepo describes the dependencies of all packages, and does
436
+ not necessarily translate to the isolated output without altering it. Different
437
+ package managers use very different formats, and it might not be enough to do a
438
+ find/replace on some paths.
439
+
440
+ It is also not possibly to generate a brand new lockfile from the isolated code
441
+ by mimicking a fresh install, because versions would be able to diverge and thus
442
+ it would negate the whole point of having a lockfile in the first place.
380
443
 
381
- The PNPM lockfile clearly has a structure describing the different packages by
382
- their relative paths, and so to correct the lockfile it is adapted before being
383
- stored to the isolate directory.
444
+ What we need is to re-generate a lockfile for the isolated output based on the
445
+ versions that are currently installed and locked in the monorepo lockfile.
446
+
447
+ ### PNPM
448
+
449
+ For PNPM a new isolated lockfile is generated.
384
450
 
385
451
  ### NPM
386
452
 
387
- It seems that when using NPM the `npm ci` can fail with a message like:
453
+ For now, NPM lockfiles are simply copied over to the isolated output. I have
454
+ seen Firebase deployments work with it, but likely you are going to run into an
455
+ error like this:
388
456
 
389
457
  > `npm ci` can only install packages when your package.json and
390
458
  > package-lock.json or npm-shrinkwrap.json are in sync. Please update your lock
391
459
  > file with `npm install` before continuing.
392
460
 
393
- I haven't been able to figure out what causes this. I have seen NPM deploys
394
- working with lockfiles, but I can not reliably reproduce it.
461
+ If you experience this issue, you can choose to exclude the lockfile from
462
+ deployment by setting `"excludeLockfile": false` in your isolate.config.json
463
+ file, or make the move to PNPM (recommended).
464
+
465
+ A real solution, regenerating an isolated lockfile, should be possible based on
466
+ the
467
+ [NPM CLI Arborist](https://github.com/npm/cli/tree/latest/workspaces/arborist)
468
+ code, so I plan to look into that in the near future.
395
469
 
396
- If you experience this issue I have two suggestions:
470
+ ### Yarn
397
471
 
398
- - Upgrade to Node 18 by setting the `"runtime": "nodejs18"` in your
399
- firebase.json config. Note that you most likely also have to re-create your
400
- lockfile using Node 18.
401
- - Exclude the lockfile from deployment by setting `"excludeLockfile": false` in
402
- your isolate.config.json file.
472
+ For now, Yarn lockfiles are simply copied over to the isolated output. I believe
473
+ I have seen Firebase deployments work with it, but it is likely you will run
474
+ into an error.
403
475
 
404
- I hope we can eventually figure out what is causing this, but more investigation
405
- is required.
476
+ If you experience an issue, you can choose to exclude the lockfile from
477
+ deployment by setting `"excludeLockfile": false` in your isolate.config.json
478
+ file, or make the move to PNPM (recommended).
406
479
 
407
- ### PNPM Lockfiles disabled for now
480
+ I am not aware of any code in the official Yarn repository for re-generating a
481
+ lockfile, and I am reluctant to work on this feature based on user-land code.
408
482
 
409
- There is still [an issue with the PNPM lockfile
410
- conversion](https://github.com/0x80/isolate-package/issues/5) which makes it
411
- unusable at the moment. Until that is resolved, the lockfile is automatically
412
- excluded for PNPM.
483
+ Personally, I do not think Yarn is very relevant anymore in 2023 and I recommend
484
+ switching to PNPM.
413
485
 
414
- _Tip:_ If you can't use a lockfile I advise you to declare dependencies using
415
- absolute versions in your manifest files. This doesn't prevent their
416
- dependencies from installing newer versions, but at least you minimize the risk
417
- of things breaking.
486
+ ### A Partial Workaround
487
+
488
+ If you can not use a lockfile, because you depend on NPM or Yarn, a partial
489
+ workaround would be to declare dependencies using exact versions in your package
490
+ manifest. This doesn't prevent your dependencies-dependencies from installing
491
+ newer versions, like a lockfile would, but at least you minimize the risk of
492
+ things breaking.
418
493
 
419
494
  ## Different Package Managers
420
495
 
421
- Isolate package has been designed to work with all package managers. It has been
422
- testing it with NPM 8, 9, Yarn 1.22, Yarn 3.6 and PNPM 8.
496
+ Isolate package has been designed to work with all package managers, although
497
+ [PNPM is recommended](https://pnpm.io/feature-comparison), especially for
498
+ monorepo environments.
423
499
 
424
- The isolate process will infer the package manager name and version from the
500
+ The isolation process will infer the package manager name and version from the
425
501
  type of lockfile found and the version that the OS reports for the installed
426
502
  executable. This information is then used to change some of its behavior. For
427
503
  example, the PNPM `pack` process is preferred over the default NPM `pack` if
@@ -430,41 +506,62 @@ PNPM in used, simply because it seems to be much faster.
430
506
  The Firebase cloud deploy pipeline will use the package manager that matches
431
507
  lockfile that was found in the deployed package.
432
508
 
433
- ### Yarn v1 and v3
434
-
435
- If you are using Yarn 3 with zero-installs, the deployed package is not aware of
436
- that, because the `.yarnrc` file and `.yarn` folder are located in the root of
437
- your monorepo, and the version is not recorded as part of the lockfile. Therefor
438
- the Firebase deploy cloud pipeline will use Yarn 1 to install your dependencies.
439
- I don't think that is an issue but it might be good to know.
440
-
441
509
  ## Using the Firebase Functions Emulator
442
510
 
443
511
  The Firebase functions emulator runs on the code that firebase.json `source`
444
- points to. Unfortunately, this is the same location as is used for uploading the
445
- code for deployment, which means the emulator is forced to use the isolated
446
- output.
512
+ points to. Unfortunately, this is the same field as is used for declaring the
513
+ code for deployment, which means the emulator is looking at the isolated output.
447
514
 
448
- As a result, any changes to your code first need to go through the isolate
449
- process in order to be picked up by the emulator. In other words, changes do not
450
- propagate automatically while the emulator is running.
515
+ As a result, any changes to your code have to go through the isolate process in
516
+ order to be picked up by the emulator. In other words, changes do not propagate
517
+ automatically while the emulator is running.
451
518
 
452
- The strategy I use at the moment is to create a "emulate" script in your
453
- manifest which does the same as the Firebase predeploy, and then starts the
454
- emulator. For example:
519
+ The workaround I use at the moment is to create a "emulate" script in the
520
+ package manifest which does the same as the Firebase predeploy, and then starts
521
+ the emulator. For example:
455
522
 
456
523
  `turbo build && isolate && firebase emulators:start --only functions`
457
524
 
458
- But you will still have to stop and restart the emulator on every code change,
459
- which is a bummer.
460
-
461
- The real solution to this, I think, involves changing the firebase-tools CLI. I
462
- see two options:
463
-
464
- 1. Give the firebase config an extra field to distinguish between code that is
465
- used by the emulator and code that is bundled for deployment.
466
- 2. Integrate the isolate process into the firebase-tools deploy command, so it
467
- is only used as part of the deployment and the `source` property can still
468
- point to the original code.
469
-
470
- I plan to take this up in the near future.
525
+ You will still have to stop and restart the emulator on every code change, which
526
+ is unfortunate of course.
527
+
528
+ A real solution to this would be to integrate isolate-package into the
529
+ firebase-tools `deploy` command, so it is only executed as part of the
530
+ deployment process and the `source` property can still point to the original
531
+ code.
532
+
533
+ I plan to work on this once isolate-package is bit more mature.
534
+
535
+ ## The internal packages strategy
536
+
537
+ Recently I changed [my example monorepo setup](https://github.com/0x80/mono-ts)
538
+ to include
539
+ [the internal packages strategy](https://turbo.build/blog/you-might-not-need-typescript-project-references),
540
+ (in which the package manifest entries point directly to TS source files, to
541
+ omit the build step), and I was pleased to discover that the approach is
542
+ compatible with `isolate-packages` with only a single change in configuration.
543
+
544
+ In summary this is how it works:
545
+
546
+ 1. The package to be deployed lists its internal dependencies as usual, but the
547
+ package manifests of those dependencies point directly to the Typescript
548
+ source (and types).
549
+ 2. You configure the bundler of your target package to include the source code
550
+ for those internal packages in its output bundle. In the case of TSUP for the
551
+ [API service in the mono-ts](https://github.com/0x80/mono-ts/blob/main/services/api/tsup.config.ts)
552
+ that configuration is: `noExternal: ["@mono/common"]`
553
+ 3. When `isolate` runs, it does the exact same thing as always. It will detect
554
+ the internal packages, copies them to the isolate output folder and adjusts
555
+ any links.
556
+ 4. When deploying to Firebase, the cloud pipeline will treat the package
557
+ manifest as usual, which installs the listed dependencies and any
558
+ dependencies listed in the linked internal package manifests.
559
+
560
+ Steps 3 and 4 are no different from a traditional setup.
561
+
562
+ Note that the manifests for the internal packages will still point to the
563
+ Typescript source files, but since the shared code was embedded in the deployed
564
+ bundle, they will never be referenced via import statements and as a result the
565
+ entry points remain unused. The only reason the packages are included in the
566
+ isolated output is so that the package manager knows what dependencies to
567
+ install.