diff --git a/Cargo.lock b/Cargo.lock index c882275b73c..c256302c5fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -68,9 +68,9 @@ checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" [[package]] name = "ast_node" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87549fcb780f81054407f313a1693d102396c223f5c49ccc5d90b46a6cbef34a" +checksum = "1a36288803cd1605bc4f0e3189970a0db8e602bb01a39f8133889f35ece7ddde" dependencies = [ "darling", "pmutil", @@ -1299,9 +1299,9 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "preset_env_base" -version = "0.2.4" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f68dc2366d2258e280ad44221403aa0af50868b3e6dc1cb9fb14a302cc01948" +checksum = "ec3fc6fae03023ee1badb75ccbefc36379c419754b30e3848b77a4327f4ec1e2" dependencies = [ "ahash", "anyhow", @@ -1690,9 +1690,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "swc_atoms" -version = "0.2.13" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d99c0ac33707dd1162a3665d6ca1a28b2f6594e9c37c4703e417fc5e1ce532e" +checksum = "454bf2d73485f6c4af9c91e70ce4fd4f17e9294d37b9f2037a3c4c2fe54b598d" dependencies = [ "once_cell", "rustc-hash", @@ -1703,9 +1703,9 @@ dependencies = [ [[package]] name = "swc_cached" -version = "0.1.1" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84fed4a980e12c737171a7b17c5e0a2f4272899266fa0632ea4e31264ebdfdb5" +checksum = "395389d54bea607246bb4a400b9b3df2e55adfe8fcce7965a5b99fed7816cf4d" dependencies = [ "ahash", "anyhow", @@ -1718,9 +1718,9 @@ dependencies = [ [[package]] name = "swc_common" -version = "0.23.0" +version = "0.27.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68e76a324fa0d7240e790c78914f39fdecfa9d87ef4efed591124b58607a4a4a" +checksum = "cba38a2f1291fcf3f78f357802b8cec72ecf5e95808e9d937783e60cd3570b93" dependencies = [ "ahash", "ast_node", @@ -1774,9 +1774,9 @@ dependencies = [ [[package]] name = "swc_ecma_ast" -version = "0.84.0" +version = "0.90.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cce1fb31e3a100feb31f94647fe27e457bc13b17a8931204fdc9bc58a15c936a" +checksum = "e6b4c117b34ccc510cf6245c2f9b1f24a933beb2ef858bbfba99fb195525ddde" dependencies = [ "bitflags", "is-macro", @@ -1791,14 +1791,15 @@ dependencies = [ [[package]] name = "swc_ecma_codegen" -version = "0.115.0" +version = "0.122.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d09abf1639f76d3d174225fdb608805f9c21d4c455f4dd2ef6ab156701f1f82a" +checksum = "0acc7d3ea2b74109e0d6803c9653711958aaa01889a226ee12c93f92d5de0fe4" dependencies = [ "memchr", "num-bigint", "once_cell", "rustc-hash", + "serde", "sourcemap", "swc_atoms", "swc_common", @@ -1822,9 +1823,9 @@ dependencies = [ [[package]] name = "swc_ecma_loader" -version = "0.35.0" +version = "0.39.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "710c86eb2b253160d4a02fa77057f1c493b3932d1b83430cbbc1e7823eb47e8c" +checksum = "ece6023a43222e3bd36e3d191fa5289c848245b97fbf0127d9c0923165648d18" dependencies = [ "ahash", "anyhow", @@ -1841,9 +1842,9 @@ dependencies = [ [[package]] name = "swc_ecma_parser" -version = "0.111.0" +version = "0.118.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc1766e5b969c59e51a5dfe9337755d7380a891e579dd6b0eb7816587c7ea7aa" +checksum = "99fe54d8da755f649c81337de073f393eb852d9194a19dfbeebaada772265730" dependencies = [ "either", "enum_kind", @@ -1860,9 +1861,9 @@ dependencies = [ [[package]] name = "swc_ecma_preset_env" -version = "0.149.0" +version = "0.161.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d27c37e693b1deda42bc2f70254234d79d2c10797701f261cbb7797b8f37bb2d" +checksum = "785793720219c467ef461003320cabee596d12d05dfdc5bd3d89ff38bd13c58c" dependencies = [ "ahash", "anyhow", @@ -1885,9 +1886,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms" -version = "0.174.0" +version = "0.185.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a30f3386dbaa8490ac3ed65240c057ea3a3b20d37c4dba50c876adce5201f673" +checksum = "6e307b4c2100104d10f5840f5cba0c7b2db00742acb88d65652bb86bc4463d5b" dependencies = [ "swc_atoms", "swc_common", @@ -1905,9 +1906,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_base" -version = "0.97.0" +version = "0.104.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66b316a99dde0ef85f1878aaa9f4bf9b15f16e999c56ed31a1433928c754ae4e" +checksum = "10d7aeb3338fbc35b2d8f881a6102588b6cd1a6d7d6edf333252ee358c6e9b2c" dependencies = [ "better_scoped_tls", "bitflags", @@ -1928,9 +1929,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_classes" -version = "0.85.0" +version = "0.93.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c853c4366e81092d38b746e71adffc1150c694f02c1068c9fa24abbdc373a65f" +checksum = "f5368a6459a31def42c29a8d0e5ebaafdbe9c731b244a2deb976151d12e37fe4" dependencies = [ "swc_atoms", "swc_common", @@ -1942,9 +1943,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_compat" -version = "0.114.0" +version = "0.124.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace2890c492568b47abb6eecbbb2dcb8f2218adcf0d8a3b73d84b88fddc7d87f" +checksum = "8c376bfd35ce0fa4fcd90868b066191e10f6eb2426b12fe9b3d031a465f65a98" dependencies = [ "ahash", "arrayvec", @@ -1982,9 +1983,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_module" -version = "0.130.0" +version = "0.141.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66810e70c1386e75a86a5ecdcfeb2150ec9f7b32a9213beedff60c33a46c7947" +checksum = "fe8f03422562c4747c78a55f6ba41625eb1a6167ec8b152df5a28a5bb36ab345" dependencies = [ "Inflector", "ahash", @@ -2010,9 +2011,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_optimization" -version = "0.143.0" +version = "0.154.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9def3dc7a6afe6b44cacd61c200181507396ee3c21a3751299718fecebce51d" +checksum = "25ad6077e00037f2249df854b60359d6aa85ffbc83ce7e9aea59734016af941a" dependencies = [ "ahash", "dashmap", @@ -2033,9 +2034,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_proposal" -version = "0.122.0" +version = "0.132.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ebc6e03a51f9adcbc40ec144c9bbe78de872bf6f8f581f3abd51187ec6e648" +checksum = "46f6e9c5772404d7a07a2cda019717cbd294d47215a7a2101332d1d4b64fdc92" dependencies = [ "either", "serde", @@ -2052,9 +2053,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_react" -version = "0.132.0" +version = "0.143.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "438ffd11b17c3c6565e44a9a0d596687459de9f13b9ea302f5baf8e20c07a860" +checksum = "11cee549bb5166dd212e5e18f49ddd3e5db9698314aa1ffa6deea553f659978e" dependencies = [ "ahash", "base64 0.13.0", @@ -2078,9 +2079,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_typescript" -version = "0.136.0" +version = "0.147.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d8c061e8ad8a3f47e9d49f85cea3ab1edca0a6585354ea23923d18e75368eb4" +checksum = "020383a6e9aa3ab8225f50de42b2528f5d3130a7d92b436a8594d2af05e244aa" dependencies = [ "serde", "swc_atoms", @@ -2094,9 +2095,9 @@ dependencies = [ [[package]] name = "swc_ecma_utils" -version = "0.93.0" +version = "0.100.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70981d5ef10c0ff0a002e21decbca9dde5b40c2fc0d0bc6eaebb219a8e0a5f7d" +checksum = "671eb0ef731ab6e9357e0a1845f527b02ed319b813b0e274d87b8d942554a353" dependencies = [ "indexmap", "once_cell", @@ -2110,9 +2111,9 @@ dependencies = [ [[package]] name = "swc_ecma_visit" -version = "0.70.0" +version = "0.76.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcd081250d664808fcd23110202728811236c87f527656ffc1db7f00ac1a06dd" +checksum = "02cb4c2c4213d603543e7232db69e763a9292953db511b0ed5d1bf8c1b227b90" dependencies = [ "num-bigint", "swc_atoms", @@ -2124,9 +2125,9 @@ dependencies = [ [[package]] name = "swc_ecmascript" -version = "0.179.0" +version = "0.192.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44e4fa994e933838459cfbfce2913f34b054ff3ecc4988e6f1eb993d7bb1a7ef" +checksum = "c1b18f993b6f6e8a5e81230226af043063f28e83eec89cf93d3cb554d2c63bdb" dependencies = [ "swc_ecma_ast", "swc_ecma_codegen", @@ -2139,9 +2140,9 @@ dependencies = [ [[package]] name = "swc_eq_ignore_macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c8f200a2eaed938e7c1a685faaa66e6d42fa9e17da5f62572d3cbc335898f5e" +checksum = "0c20468634668c2bbab581947bb8c75c97158d5a6959f4ba33df20983b20b4f6" dependencies = [ "pmutil", "proc-macro2", @@ -2151,9 +2152,9 @@ dependencies = [ [[package]] name = "swc_macros_common" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5dca3f08d02da4684c3373150f7c045128f81ea00f0c434b1b012bc65a6cce3" +checksum = "a4be988307882648d9bc7c71a6a73322b7520ef0211e920489a98f8391d8caa2" dependencies = [ "pmutil", "proc-macro2", @@ -2163,9 +2164,9 @@ dependencies = [ [[package]] name = "swc_trace_macro" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d1a05fdb40442d687cb2eff4e5c374886a66ced1436ad87515de7d72b3ec10b" +checksum = "a4795c8d23e0de62eef9cac0a20ae52429ee2ffc719768e838490f195b7d7267" dependencies = [ "proc-macro2", "quote", @@ -2174,9 +2175,9 @@ dependencies = [ [[package]] name = "swc_visit" -version = "0.4.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fafa6c946bdbe601f5511140776d59e82a03f52a5e5039192b4b96f3ca639d88" +checksum = "b754ef01f2614eb469fd5215789bfd319566a3bf1033056d7a7bfb5a3c9a69f5" dependencies = [ "either", "swc_visit_macros", @@ -2184,9 +2185,9 @@ dependencies = [ [[package]] name = "swc_visit_macros" -version = "0.4.0" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cad1b8e0b2d48660bc454f70495e9bb583f9bf501f28165568569946e62f44a2" +checksum = "c230bcd129d1fbcd1decd8b43cccd613fda11c895f7c04d6c966231dbc1959af" dependencies = [ "Inflector", "pmutil", diff --git a/package.json b/package.json index 762dd114e2d..d7c5370a3c6 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "mocha-junit-reporter": "^2.0.0", "mocha-multi-reporters": "^1.5.1", "prettier": "2.4.1", + "punycode": "^1.4.1", "rimraf": "^3.0.2", "semver": "^5.7.1", "sinon": "^7.3.1" diff --git a/packages/core/core/src/InternalConfig.js b/packages/core/core/src/InternalConfig.js index a8f819e11f7..6ba525b58ef 100644 --- a/packages/core/core/src/InternalConfig.js +++ b/packages/core/core/src/InternalConfig.js @@ -25,6 +25,7 @@ type ConfigOpts = {| invalidateOnOptionChange?: Set, devDeps?: Array, invalidateOnStartup?: boolean, + invalidateOnBuild?: boolean, |}; export function createConfig({ @@ -39,6 +40,7 @@ export function createConfig({ invalidateOnOptionChange, devDeps, invalidateOnStartup, + invalidateOnBuild, }: ConfigOpts): Config { let environment = env ?? createEnvironment(); return { @@ -59,5 +61,6 @@ export function createConfig({ invalidateOnOptionChange: invalidateOnOptionChange ?? new Set(), devDeps: devDeps ?? [], invalidateOnStartup: invalidateOnStartup ?? false, + invalidateOnBuild: invalidateOnBuild ?? false, }; } diff --git a/packages/core/core/src/PackagerRunner.js b/packages/core/core/src/PackagerRunner.js index 3c18a16c10b..e31746f51f2 100644 --- a/packages/core/core/src/PackagerRunner.js +++ b/packages/core/core/src/PackagerRunner.js @@ -38,6 +38,7 @@ import BundleGraph, { bundleGraphToInternalBundleGraph, } from './public/BundleGraph'; import PluginOptions from './public/PluginOptions'; +import PublicConfig from './public/Config'; import {PARCEL_VERSION, HASH_REF_PREFIX, HASH_REF_REGEX} from './constants'; import { fromProjectPath, @@ -50,7 +51,7 @@ import { loadPluginConfig, getConfigHash, getConfigRequests, - type PluginWithLoadConfig, + type PluginWithBundleConfig, } from './requests/ConfigRequest'; import { createDevDependency, @@ -143,12 +144,20 @@ export default class PackagerRunner { ): Promise { invalidateDevDeps(invalidDevDeps, this.options, this.config); - let configs = await this.loadConfigs(bundleGraph, bundle); + let {configs, bundleConfigs} = await this.loadConfigs(bundleGraph, bundle); let bundleInfo = - (await this.getBundleInfoFromCache(bundleGraph, bundle, configs)) ?? - (await this.getBundleInfo(bundle, bundleGraph, configs)); - - let configRequests = getConfigRequests([...configs.values()]); + (await this.getBundleInfoFromCache( + bundleGraph, + bundle, + configs, + bundleConfigs, + )) ?? + (await this.getBundleInfo(bundle, bundleGraph, configs, bundleConfigs)); + + let configRequests = getConfigRequests([ + ...configs.values(), + ...bundleConfigs.values(), + ]); let devDepRequests = getWorkerDevDepRequests([ ...this.devDepRequests.values(), ]); @@ -164,70 +173,110 @@ export default class PackagerRunner { async loadConfigs( bundleGraph: InternalBundleGraph, bundle: InternalBundle, - ): Promise> { + ): Promise<{| + configs: Map, + bundleConfigs: Map, + |}> { let configs = new Map(); + let bundleConfigs = new Map(); - await this.loadConfig(bundle, configs); + await this.loadConfig(bundleGraph, bundle, configs, bundleConfigs); for (let inlineBundle of bundleGraph.getInlineBundles(bundle)) { - await this.loadConfig(inlineBundle, configs); + await this.loadConfig(bundleGraph, inlineBundle, configs, bundleConfigs); } - return configs; + return {configs, bundleConfigs}; } async loadConfig( + bundleGraph: InternalBundleGraph, bundle: InternalBundle, configs: Map, + bundleConfigs: Map, ): Promise { let name = nullthrows(bundle.name); let plugin = await this.config.getPackager(name); - await this.loadPluginConfig(plugin, configs); + await this.loadPluginConfig( + bundleGraph, + bundle, + plugin, + configs, + bundleConfigs, + ); let optimizers = await this.config.getOptimizers(name, bundle.pipeline); - for (let optimizer of optimizers) { - await this.loadPluginConfig(optimizer, configs); + await this.loadPluginConfig( + bundleGraph, + bundle, + optimizer, + configs, + bundleConfigs, + ); } } - async loadPluginConfig( + async loadPluginConfig( + bundleGraph: InternalBundleGraph, + bundle: InternalBundle, plugin: LoadedPlugin, configs: Map, + bundleConfigs: Map, ): Promise { - if (configs.has(plugin.name)) { - return; - } + if (!configs.has(plugin.name)) { + // Only load config for a plugin once per build. + let existing = pluginConfigs.get(plugin.name); + if (existing != null) { + configs.set(plugin.name, existing); + } else { + if (plugin.plugin.loadConfig != null) { + let config = createConfig({ + plugin: plugin.name, + searchPath: toProjectPathUnsafe('index'), + }); + + await loadPluginConfig(plugin, config, this.options); + + for (let devDep of config.devDeps) { + let devDepRequest = await createDevDependency( + devDep, + this.previousDevDeps, + this.options, + ); + let key = `${devDep.specifier}:${fromProjectPath( + this.options.projectRoot, + devDep.resolveFrom, + )}`; + this.devDepRequests.set(key, devDepRequest); + } - // Only load config for a plugin once per build. - let existing = pluginConfigs.get(plugin.name); - if (existing != null) { - configs.set(plugin.name, existing); - return; + pluginConfigs.set(plugin.name, config); + configs.set(plugin.name, config); + } + } } - if (plugin.plugin.loadConfig != null) { + let loadBundleConfig = plugin.plugin.loadBundleConfig; + if (!bundleConfigs.has(plugin.name) && loadBundleConfig != null) { let config = createConfig({ plugin: plugin.name, - searchPath: toProjectPathUnsafe('index'), + searchPath: joinProjectPath( + bundle.target.distDir, + bundle.name ?? bundle.id, + ), }); - - await loadPluginConfig(plugin, config, this.options); - - for (let devDep of config.devDeps) { - let devDepRequest = await createDevDependency( - devDep, - this.previousDevDeps, + config.result = await loadBundleConfig({ + bundle: NamedBundle.get(bundle, bundleGraph, this.options), + bundleGraph: new BundleGraph( + bundleGraph, + NamedBundle.get.bind(NamedBundle), this.options, - ); - let key = `${devDep.specifier}:${fromProjectPath( - this.options.projectRoot, - devDep.resolveFrom, - )}`; - this.devDepRequests.set(key, devDepRequest); - } - - pluginConfigs.set(plugin.name, config); - configs.set(plugin.name, config); + ), + config: new PublicConfig(config, this.options), + options: new PluginOptions(this.options), + logger: new PluginLogger({origin: plugin.name}), + }); + bundleConfigs.set(plugin.name, config); } } @@ -235,6 +284,7 @@ export default class PackagerRunner { bundleGraph: InternalBundleGraph, bundle: InternalBundle, configs: Map, + bundleConfigs: Map, ): Async { if (this.options.shouldDisableCache) { return; @@ -244,6 +294,7 @@ export default class PackagerRunner { bundle, bundleGraph, configs, + bundleConfigs, this.previousInvalidations, ); let infoKey = PackagerRunner.getInfoKey(cacheKey); @@ -254,17 +305,23 @@ export default class PackagerRunner { bundle: InternalBundle, bundleGraph: InternalBundleGraph, configs: Map, + bundleConfigs: Map, ): Promise { let {type, contents, map} = await this.getBundleResult( bundle, bundleGraph, configs, + bundleConfigs, ); // Recompute cache keys as they may have changed due to dev dependencies. - let cacheKey = await this.getCacheKey(bundle, bundleGraph, configs, [ - ...this.invalidations.values(), - ]); + let cacheKey = await this.getCacheKey( + bundle, + bundleGraph, + configs, + bundleConfigs, + [...this.invalidations.values()], + ); let cacheKeys = { content: PackagerRunner.getContentKey(cacheKey), map: PackagerRunner.getMapKey(cacheKey), @@ -278,12 +335,18 @@ export default class PackagerRunner { bundle: InternalBundle, bundleGraph: InternalBundleGraph, configs: Map, + bundleConfigs: Map, ): Promise<{| type: string, contents: Blob, map: ?string, |}> { - let packaged = await this.package(bundle, bundleGraph, configs); + let packaged = await this.package( + bundle, + bundleGraph, + configs, + bundleConfigs, + ); let type = packaged.type ?? bundle.type; let res = await this.optimize( bundle, @@ -292,6 +355,7 @@ export default class PackagerRunner { packaged.contents, packaged.map, configs, + bundleConfigs, ); let map = @@ -319,6 +383,7 @@ export default class PackagerRunner { internalBundle: InternalBundle, bundleGraph: InternalBundleGraph, configs: Map, + bundleConfigs: Map, ): Promise { let bundle = NamedBundle.get(internalBundle, bundleGraph, this.options); this.report({ @@ -332,6 +397,7 @@ export default class PackagerRunner { try { return await plugin.package({ config: configs.get(name)?.result, + bundleConfig: bundleConfigs.get(name)?.result, bundle, bundleGraph: new BundleGraph( bundleGraph, @@ -358,6 +424,7 @@ export default class PackagerRunner { // $FlowFixMe bundleGraphToInternalBundleGraph(bundleGraph), configs, + bundleConfigs, ); return {contents: res.contents}; @@ -395,6 +462,7 @@ export default class PackagerRunner { contents: Blob, map?: ?SourceMap, configs: Map, + bundleConfigs: Map, ): Promise { let bundle = NamedBundle.get( internalBundle, @@ -430,6 +498,7 @@ export default class PackagerRunner { try { let next = await optimizer.plugin.optimize({ config: configs.get(optimizer.name)?.result, + bundleConfig: bundleConfigs.get(optimizer.name)?.result, bundle, bundleGraph, contents: optimized.contents, @@ -535,6 +604,7 @@ export default class PackagerRunner { bundle: InternalBundle, bundleGraph: InternalBundleGraph, configs: Map, + bundleConfigs: Map, invalidations: Array, ): Promise { let configResults = {}; @@ -547,6 +617,16 @@ export default class PackagerRunner { ); } } + let globalInfoResults = {}; + for (let [pluginName, config] of bundleConfigs) { + if (config) { + globalInfoResults[pluginName] = await getConfigHash( + config, + pluginName, + this.options, + ); + } + } let devDepHashes = await this.getDevDepHashes(bundle); for (let inlineBundle of bundleGraph.getInlineBundles(bundle)) { @@ -565,6 +645,7 @@ export default class PackagerRunner { bundle.target.publicUrl + bundleGraph.getHash(bundle) + JSON.stringify(configResults) + + JSON.stringify(globalInfoResults) + this.options.mode, ); } diff --git a/packages/core/core/src/Parcel.js b/packages/core/core/src/Parcel.js index ea0b1219072..8688c081716 100644 --- a/packages/core/core/src/Parcel.js +++ b/packages/core/core/src/Parcel.js @@ -261,6 +261,8 @@ export default class Parcel { type: 'buildStart', }); + this.#requestTracker.graph.invalidateOnBuildNodes(); + let request = createParcelBuildRequest({ optionsRef: this.#optionsRef, requestedAssetIds: this.#requestedAssetIds, diff --git a/packages/core/core/src/ParcelConfig.js b/packages/core/core/src/ParcelConfig.js index cec4ebd4f0a..c69a5f0a9a0 100644 --- a/packages/core/core/src/ParcelConfig.js +++ b/packages/core/core/src/ParcelConfig.js @@ -253,7 +253,7 @@ export default class ParcelConfig { async getPackager( filePath: FilePath, - ): Promise>> { + ): Promise>> { let packager = this.matchGlobMap( toProjectPathUnsafe(filePath), this.packagers, @@ -265,7 +265,7 @@ export default class ParcelConfig { '/packagers', ); } - return this.loadPlugin>(packager); + return this.loadPlugin>(packager); } _getOptimizerNodes( @@ -298,13 +298,13 @@ export default class ParcelConfig { getOptimizers( filePath: FilePath, pipeline: ?string, - ): Promise>>> { + ): Promise>>> { let optimizers = this._getOptimizerNodes(filePath, pipeline); if (optimizers.length === 0) { return Promise.resolve([]); } - return this.loadPlugins>(optimizers); + return this.loadPlugins>(optimizers); } async getCompressors( diff --git a/packages/core/core/src/RequestTracker.js b/packages/core/core/src/RequestTracker.js index 00a950e00dd..08e5c73b453 100644 --- a/packages/core/core/src/RequestTracker.js +++ b/packages/core/core/src/RequestTracker.js @@ -70,6 +70,7 @@ type RequestGraphOpts = {| envNodeIds: Set, optionNodeIds: Set, unpredicatableNodeIds: Set, + invalidateOnBuildNodeIds: Set, |}; type SerializedRequestGraph = {| @@ -80,6 +81,7 @@ type SerializedRequestGraph = {| envNodeIds: Set, optionNodeIds: Set, unpredicatableNodeIds: Set, + invalidateOnBuildNodeIds: Set, |}; type FileNode = {|id: ContentKey, +type: 'file', value: InternalFile|}; @@ -135,6 +137,7 @@ export type RunAPI = {| invalidateOnFileDelete: ProjectPath => void, invalidateOnFileUpdate: ProjectPath => void, invalidateOnStartup: () => void, + invalidateOnBuild: () => void, invalidateOnEnvChange: string => void, invalidateOnOptionChange: string => void, getInvalidations(): Array, @@ -216,6 +219,7 @@ export class RequestGraph extends ContentGraph< // Unpredictable nodes are requests that cannot be predicted whether they should rerun based on // filesystem changes alone. They should rerun on each startup of Parcel. unpredicatableNodeIds: Set = new Set(); + invalidateOnBuildNodeIds: Set = new Set(); // $FlowFixMe[prop-missing] static deserialize(opts: RequestGraphOpts): RequestGraph { @@ -227,6 +231,7 @@ export class RequestGraph extends ContentGraph< deserialized.envNodeIds = opts.envNodeIds; deserialized.optionNodeIds = opts.optionNodeIds; deserialized.unpredicatableNodeIds = opts.unpredicatableNodeIds; + deserialized.invalidateOnBuildNodeIds = opts.invalidateOnBuildNodeIds; return deserialized; } @@ -240,6 +245,7 @@ export class RequestGraph extends ContentGraph< envNodeIds: this.envNodeIds, optionNodeIds: this.optionNodeIds, unpredicatableNodeIds: this.unpredicatableNodeIds, + invalidateOnBuildNodeIds: this.invalidateOnBuildNodeIds, }; } @@ -267,6 +273,7 @@ export class RequestGraph extends ContentGraph< this.incompleteNodeIds.delete(nodeId); this.incompleteNodePromises.delete(nodeId); this.unpredicatableNodeIds.delete(nodeId); + this.invalidateOnBuildNodeIds.delete(nodeId); let node = nullthrows(this.getNode(nodeId)); if (node.type === 'glob') { this.globNodeIds.delete(nodeId); @@ -326,6 +333,14 @@ export class RequestGraph extends ContentGraph< } } + invalidateOnBuildNodes() { + for (let nodeId of this.invalidateOnBuildNodeIds) { + let node = nullthrows(this.getNode(nodeId)); + invariant(node.type !== 'file' && node.type !== 'glob'); + this.invalidateNode(nodeId, STARTUP); + } + } + invalidateEnvNodes(env: EnvMap) { for (let nodeId of this.envNodeIds) { let node = nullthrows(this.getNode(nodeId)); @@ -504,6 +519,11 @@ export class RequestGraph extends ContentGraph< this.unpredicatableNodeIds.add(requestNodeId); } + invalidateOnBuild(requestNodeId: NodeId) { + this.getRequestNode(requestNodeId); + this.invalidateOnBuildNodeIds.add(requestNodeId); + } + invalidateOnEnvChange( requestNodeId: NodeId, env: string, @@ -552,6 +572,7 @@ export class RequestGraph extends ContentGraph< clearInvalidations(nodeId: NodeId) { this.unpredicatableNodeIds.delete(nodeId); + this.invalidateOnBuildNodeIds.delete(nodeId); this.replaceNodeIdsConnectedTo( nodeId, [], @@ -993,6 +1014,7 @@ export default class RequestTracker { invalidateOnFileUpdate: filePath => this.graph.invalidateOnFileUpdate(requestId, filePath), invalidateOnStartup: () => this.graph.invalidateOnStartup(requestId), + invalidateOnBuild: () => this.graph.invalidateOnBuild(requestId), invalidateOnEnvChange: env => this.graph.invalidateOnEnvChange(requestId, env, this.options.env[env]), invalidateOnOptionChange: option => @@ -1120,6 +1142,7 @@ async function loadRequestGraph(options): Async { opts, ); requestGraph.invalidateUnpredictableNodes(); + requestGraph.invalidateOnBuildNodes(); requestGraph.invalidateEnvNodes(options.env); requestGraph.invalidateOptionNodes(options); requestGraph.respondToFSEvents( diff --git a/packages/core/core/src/public/Config.js b/packages/core/core/src/public/Config.js index 1b2eba4edb8..0b1d34242b9 100644 --- a/packages/core/core/src/public/Config.js +++ b/packages/core/core/src/public/Config.js @@ -112,6 +112,10 @@ export default class PublicConfig implements IConfig { this.#config.invalidateOnStartup = true; } + invalidateOnBuild() { + this.#config.invalidateOnBuild = true; + } + async getConfigFrom( searchPath: FilePath, fileNames: Array, diff --git a/packages/core/core/src/public/Environment.js b/packages/core/core/src/public/Environment.js index 4782de5ad46..2fdcb0db617 100644 --- a/packages/core/core/src/public/Environment.js +++ b/packages/core/core/src/public/Environment.js @@ -52,6 +52,7 @@ const ALL_BROWSERS = [ 'kaios', ]; +// See require("caniuse-api").getSupport() const supportData = { esmodules: { edge: '16', @@ -119,6 +120,7 @@ const supportData = { qq: '10.4', baidu: '7.12', kaios: '2.5', + and_chr: '50', and_qq: '12.12', op_mob: '64', }, diff --git a/packages/core/core/src/requests/ConfigRequest.js b/packages/core/core/src/requests/ConfigRequest.js index 86861d2c7bd..620034b9b17 100644 --- a/packages/core/core/src/requests/ConfigRequest.js +++ b/packages/core/core/src/requests/ConfigRequest.js @@ -4,6 +4,8 @@ import type { Config as IConfig, PluginOptions as IPluginOptions, PluginLogger as IPluginLogger, + NamedBundle as INamedBundle, + BundleGraph as IBundleGraph, } from '@parcel/types'; import type { Config, @@ -32,6 +34,22 @@ export type PluginWithLoadConfig = { ... }; +export type PluginWithBundleConfig = { + loadConfig?: ({| + config: IConfig, + options: IPluginOptions, + logger: IPluginLogger, + |}) => Async, + loadBundleConfig?: ({| + bundle: INamedBundle, + bundleGraph: IBundleGraph, + config: IConfig, + options: IPluginOptions, + logger: IPluginLogger, + |}) => Async, + ... +}; + export type ConfigRequest = { id: string, invalidateOnFileChange: Set, @@ -39,6 +57,7 @@ export type ConfigRequest = { invalidateOnEnvChange: Set, invalidateOnOptionChange: Set, invalidateOnStartup: boolean, + invalidateOnBuild: boolean, ... }; @@ -81,6 +100,7 @@ export async function runConfigRequest( invalidateOnEnvChange, invalidateOnOptionChange, invalidateOnStartup, + invalidateOnBuild, } = configRequest; // If there are no invalidations, then no need to create a node. @@ -88,7 +108,8 @@ export async function runConfigRequest( invalidateOnFileChange.size === 0 && invalidateOnFileCreate.length === 0 && invalidateOnOptionChange.size === 0 && - !invalidateOnStartup + !invalidateOnStartup && + !invalidateOnBuild ) { return; } @@ -117,6 +138,10 @@ export async function runConfigRequest( if (invalidateOnStartup) { api.invalidateOnStartup(); } + + if (invalidateOnBuild) { + api.invalidateOnBuild(); + } }, input: null, }); @@ -178,7 +203,8 @@ export function getConfigRequests( config.invalidateOnFileCreate.length > 0 || config.invalidateOnEnvChange.size > 0 || config.invalidateOnOptionChange.size > 0 || - config.invalidateOnStartup + config.invalidateOnStartup || + config.invalidateOnBuild ); }) .map(config => ({ @@ -188,5 +214,6 @@ export function getConfigRequests( invalidateOnEnvChange: config.invalidateOnEnvChange, invalidateOnOptionChange: config.invalidateOnOptionChange, invalidateOnStartup: config.invalidateOnStartup, + invalidateOnBuild: config.invalidateOnBuild, })); } diff --git a/packages/core/core/src/requests/PackageRequest.js b/packages/core/core/src/requests/PackageRequest.js index e02c605fd30..209f36ebc31 100644 --- a/packages/core/core/src/requests/PackageRequest.js +++ b/packages/core/core/src/requests/PackageRequest.js @@ -7,7 +7,7 @@ import type {SharedReference} from '@parcel/workers'; import type {StaticRunOpts} from '../RequestTracker'; import type {Bundle} from '../types'; import type BundleGraph from '../BundleGraph'; -import type {BundleInfo} from '../PackagerRunner'; +import type {BundleInfo, PackageRequestResult} from '../PackagerRunner'; import type {ConfigAndCachePath} from './ParcelConfigRequest'; import nullthrows from 'nullthrows'; @@ -55,7 +55,7 @@ async function run({input, api, farm}: RunInput) { await api.runRequest(createParcelConfigRequest()), ); let {devDepRequests, configRequests, bundleInfo, invalidations} = - await runPackage({ + (await runPackage({ bundle, bundleGraphReference, optionsRef, @@ -63,7 +63,7 @@ async function run({input, api, farm}: RunInput) { previousDevDeps: devDeps, invalidDevDeps, previousInvalidations: api.getInvalidations(), - }); + }): PackageRequestResult); for (let devDepRequest of devDepRequests) { await runDevDepRequest(api, devDepRequest); @@ -90,6 +90,7 @@ async function run({input, api, farm}: RunInput) { } } + // $FlowFixMe[cannot-write] time is marked read-only, but this is the exception bundleInfo.time = Date.now() - start; api.storeResult(bundleInfo); diff --git a/packages/core/core/src/requests/ValidationRequest.js b/packages/core/core/src/requests/ValidationRequest.js index d657b88ad68..9b7acdd9d10 100644 --- a/packages/core/core/src/requests/ValidationRequest.js +++ b/packages/core/core/src/requests/ValidationRequest.js @@ -47,12 +47,13 @@ export default function createValidationRequest( }); // Schedule validations on workers for all plugins that implement the one-asset-at-a-time "validate" method. - let promises = trackedRequestsDesc.map(async request => - (await farm.createHandle('runValidate'))({ - requests: [request], - optionsRef: optionsRef, - configCachePath: cachePath, - }), + let promises = trackedRequestsDesc.map( + async request => + ((await farm.createHandle('runValidate'))({ + requests: [request], + optionsRef: optionsRef, + configCachePath: cachePath, + }): void), ); // Skip sending validation requests if no validators were configured diff --git a/packages/core/core/src/requests/WriteBundlesRequest.js b/packages/core/core/src/requests/WriteBundlesRequest.js index bdfcbcf1360..799caa346ed 100644 --- a/packages/core/core/src/requests/WriteBundlesRequest.js +++ b/packages/core/core/src/requests/WriteBundlesRequest.js @@ -92,6 +92,7 @@ async function run({input, api, farm, options}: RunInput) { bundleGraphReference: ref, optionsRef, }); + let info = await api.runRequest(request); bundleInfoMap[bundle.id] = info; diff --git a/packages/core/core/src/types.js b/packages/core/core/src/types.js index 7ccf54fe14b..1a23828aba0 100644 --- a/packages/core/core/src/types.js +++ b/packages/core/core/src/types.js @@ -445,6 +445,7 @@ export type Config = {| invalidateOnOptionChange: Set, devDeps: Array, invalidateOnStartup: boolean, + invalidateOnBuild: boolean, |}; export type EntryRequest = {| diff --git a/packages/core/core/test/PublicEnvironment.test.js b/packages/core/core/test/PublicEnvironment.test.js new file mode 100644 index 00000000000..c3a3f72a79d --- /dev/null +++ b/packages/core/core/test/PublicEnvironment.test.js @@ -0,0 +1,27 @@ +// @flow strict-local + +import assert from 'assert'; +import {createEnvironment} from '../src/Environment'; +import PublicEnvironment from '../src/public/Environment'; +import {DEFAULT_OPTIONS} from './test-utils'; + +describe('Public Environment', () => { + it('has correct support data for ChromeAndroid', () => { + let env = new PublicEnvironment( + createEnvironment({ + context: 'browser', + engines: { + browsers: ['last 1 Chrome version', 'last 1 ChromeAndroid version'], + }, + outputFormat: 'esmodule', + }), + DEFAULT_OPTIONS, + ); + + assert(env.supports('esmodules')); + assert(env.supports('dynamic-import')); + assert(env.supports('worker-module')); + assert(env.supports('import-meta-url')); + assert(env.supports('arrow-functions')); + }); +}); diff --git a/packages/core/core/test/TargetRequest.test.js b/packages/core/core/test/TargetRequest.test.js index 63205e98cf2..b8db4e11e69 100644 --- a/packages/core/core/test/TargetRequest.test.js +++ b/packages/core/core/test/TargetRequest.test.js @@ -77,6 +77,7 @@ describe('TargetResolver', () => { invalidateOnEnvChange() {}, invalidateOnOptionChange() {}, invalidateOnStartup() {}, + invalidateOnBuild() {}, getInvalidations() { return []; }, diff --git a/packages/core/integration-tests/test/integration/packager-loadBundleConfig/.parcelrc b/packages/core/integration-tests/test/integration/packager-loadBundleConfig/.parcelrc new file mode 100644 index 00000000000..8d13690ba88 --- /dev/null +++ b/packages/core/integration-tests/test/integration/packager-loadBundleConfig/.parcelrc @@ -0,0 +1,9 @@ +{ + "extends": "@parcel/config-default", + "transformers": { + "*.txt": ["@parcel/transformer-raw"] + }, + "packagers": { + "*.txt": "parcel-packager-config" + } +} diff --git a/packages/core/integration-tests/test/integration/packager-loadBundleConfig/a.txt b/packages/core/integration-tests/test/integration/packager-loadBundleConfig/a.txt new file mode 100644 index 00000000000..a661f8d4fff --- /dev/null +++ b/packages/core/integration-tests/test/integration/packager-loadBundleConfig/a.txt @@ -0,0 +1 @@ +Hello from a diff --git a/packages/core/integration-tests/test/integration/packager-loadBundleConfig/b.txt b/packages/core/integration-tests/test/integration/packager-loadBundleConfig/b.txt new file mode 100644 index 00000000000..a5ae4447f96 --- /dev/null +++ b/packages/core/integration-tests/test/integration/packager-loadBundleConfig/b.txt @@ -0,0 +1 @@ +Hello from b diff --git a/packages/core/integration-tests/test/integration/packager-loadBundleConfig/index.2.html b/packages/core/integration-tests/test/integration/packager-loadBundleConfig/index.2.html new file mode 100644 index 00000000000..8b9833e4eaf --- /dev/null +++ b/packages/core/integration-tests/test/integration/packager-loadBundleConfig/index.2.html @@ -0,0 +1,2 @@ +a +a diff --git a/packages/core/integration-tests/test/integration/packager-loadBundleConfig/index.html b/packages/core/integration-tests/test/integration/packager-loadBundleConfig/index.html new file mode 100644 index 00000000000..4d38cd63b80 --- /dev/null +++ b/packages/core/integration-tests/test/integration/packager-loadBundleConfig/index.html @@ -0,0 +1 @@ +a diff --git a/packages/core/integration-tests/test/integration/packager-loadBundleConfig/node_modules/parcel-packager-config/index.js b/packages/core/integration-tests/test/integration/packager-loadBundleConfig/node_modules/parcel-packager-config/index.js new file mode 100644 index 00000000000..34b182ac2e9 --- /dev/null +++ b/packages/core/integration-tests/test/integration/packager-loadBundleConfig/node_modules/parcel-packager-config/index.js @@ -0,0 +1,24 @@ +// @flow strict-local + +const invariant = require('assert'); +const {Packager} = require('@parcel/plugin'); + +module.exports = (new Packager({ + loadBundleConfig({bundle, bundleGraph, config}) { + config.invalidateOnBuild(); + let x = bundleGraph + .getBundles() + .filter(b => b.type === 'txt' && b.needsStableName) + .map(b => b.name); + console.log(bundle.name, x); + return x; + }, + async package({bundle, bundleConfig}) { + let contents = await bundle.getMainEntry()?.getCode(); + invariant(contents != null); + + return { + contents: `Bundles: ${bundleConfig.join(',')}. Contents: ${contents}`, + }; + }, +}) /*: Packager */); diff --git a/packages/core/integration-tests/test/integration/packager-loadBundleConfig/node_modules/parcel-packager-config/package.json b/packages/core/integration-tests/test/integration/packager-loadBundleConfig/node_modules/parcel-packager-config/package.json new file mode 100644 index 00000000000..3eda97b42bc --- /dev/null +++ b/packages/core/integration-tests/test/integration/packager-loadBundleConfig/node_modules/parcel-packager-config/package.json @@ -0,0 +1,13 @@ +{ + "name": "parcel-packager-config", + "version": "1.0.0", + "private": true, + "main": "index.js", + "engines": { + "parcel": "^2.0.0-beta.1" + }, + "dependencies": { + "@parcel/plugin": "^2.0.0-beta.1", + "@parcel/utils": "^2.0.0-beta.1" + } +} diff --git a/packages/core/integration-tests/test/integration/packager-loadBundleConfig/yarn.lock b/packages/core/integration-tests/test/integration/packager-loadBundleConfig/yarn.lock new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/core/integration-tests/test/plugin.js b/packages/core/integration-tests/test/plugin.js index ec38c5ce0b2..df8b80656ea 100644 --- a/packages/core/integration-tests/test/plugin.js +++ b/packages/core/integration-tests/test/plugin.js @@ -1,13 +1,16 @@ // @flow import assert from 'assert'; +import invariant from 'assert'; import path from 'path'; import nullthrows from 'nullthrows'; import { assertBundles, bundle, + bundler, distDir, findAsset, + getNextBuild, outputFS as fs, overlayFS, run, @@ -86,6 +89,67 @@ parcel-transformer-b`, assert.deepEqual(calls, [1234]); }); + it('invalidate the cache based on loadBundleConfig in a packager', async function () { + let fixture = path.join( + __dirname, + '/integration/packager-loadBundleConfig', + ); + let entry = path.join(fixture, 'index.html'); + + let b = await bundler(entry, { + inputFS: overlayFS, + shouldDisableCache: false, + }); + + let subscription = await b.watch(); + try { + let bundleEvent = await getNextBuild(b); + invariant(bundleEvent.type === 'buildSuccess'); + + assert.strictEqual( + await overlayFS.readFile( + nullthrows( + bundleEvent.bundleGraph + .getBundles() + .find(b => b.getMainEntry()?.filePath.endsWith('a.txt')), + ).filePath, + 'utf8', + ), + `Bundles: a.txt. Contents: Hello from a\n`, + ); + + await overlayFS.copyFile(path.join(fixture, 'index.2.html'), entry); + + bundleEvent = await getNextBuild(b); + invariant(bundleEvent.type === 'buildSuccess'); + + assert.strictEqual( + await overlayFS.readFile( + nullthrows( + bundleEvent.bundleGraph + .getBundles() + .find(b => b.getMainEntry()?.filePath.endsWith('a.txt')), + ).filePath, + 'utf8', + ), + `Bundles: a.txt,b.txt. Contents: Hello from a\n`, + ); + assert.strictEqual( + await overlayFS.readFile( + nullthrows( + bundleEvent.bundleGraph + .getBundles() + .find(b => b.getMainEntry()?.filePath.endsWith('b.txt')), + ).filePath, + 'utf8', + ), + `Bundles: a.txt,b.txt. Contents: Hello from b\n`, + ); + } finally { + await subscription.unsubscribe(); + } + }); + it('invalidate the cache based on loadConfig in a packager', async function () { let fixture = path.join(__dirname, '/integration/packager-loadConfig'); let entry = path.join(fixture, 'index.txt'); diff --git a/packages/core/package-manager/src/NodePackageManager.js b/packages/core/package-manager/src/NodePackageManager.js index 9e5d665f542..39e4f5f1380 100644 --- a/packages/core/package-manager/src/NodePackageManager.js +++ b/packages/core/package-manager/src/NodePackageManager.js @@ -22,6 +22,7 @@ import Module from 'module'; import path from 'path'; import semver from 'semver'; +import {getModuleParts} from '@parcel/utils'; import {getConflictingLocalDependencies} from './utils'; import {installPackage} from './installPackage'; import pkg from '../package.json'; @@ -138,7 +139,7 @@ export class NodePackageManager implements PackageManager { } async resolve( - name: DependencySpecifier, + id: DependencySpecifier, from: FilePath, options?: ?{| range?: ?SemverRange, @@ -147,11 +148,12 @@ export class NodePackageManager implements PackageManager { |}, ): Promise { let basedir = path.dirname(from); - let key = basedir + ':' + name; + let key = basedir + ':' + id; let resolved = cache.get(key); if (!resolved) { + let [name] = getModuleParts(id); try { - resolved = await this.resolver.resolve(name, from); + resolved = await this.resolver.resolve(id, from); } catch (e) { if ( e.code !== 'MODULE_NOT_FOUND' || @@ -189,7 +191,7 @@ export class NodePackageManager implements PackageManager { saveDev: options?.saveDev ?? true, }); - return this.resolve(name, from, { + return this.resolve(id, from, { ...options, shouldAutoInstall: false, }); @@ -230,7 +232,7 @@ export class NodePackageManager implements PackageManager { if (conflicts == null && options?.shouldAutoInstall === true) { await this.install([{name, range}], from); - return this.resolve(name, from, { + return this.resolve(id, from, { ...options, shouldAutoInstall: false, }); diff --git a/packages/core/package-manager/src/NodeResolverBase.js b/packages/core/package-manager/src/NodeResolverBase.js index ff4a1a0291c..e79c5b3f9bf 100644 --- a/packages/core/package-manager/src/NodeResolverBase.js +++ b/packages/core/package-manager/src/NodeResolverBase.js @@ -13,7 +13,7 @@ import type {ResolveResult} from './types'; import Module from 'module'; import path from 'path'; import invariant from 'assert'; -import {normalizeSeparators} from '@parcel/utils'; +import {getModuleParts} from '@parcel/utils'; const builtins = {pnpapi: true}; for (let builtin of Module.builtinModules) { @@ -102,22 +102,6 @@ export class NodeResolverBase { }); } - getModuleParts(name: string): [FilePath, ?string] { - name = path.normalize(name); - let splitOn = name.indexOf(path.sep); - if (name.charAt(0) === '@') { - splitOn = name.indexOf(path.sep, splitOn + 1); - } - if (splitOn < 0) { - return [normalizeSeparators(name), undefined]; - } else { - return [ - normalizeSeparators(name.substring(0, splitOn)), - name.substring(splitOn + 1) || undefined, - ]; - } - } - isBuiltin(name: DependencySpecifier): boolean { return !!(builtins[name] || name.startsWith('node:')); } @@ -135,7 +119,7 @@ export class NodeResolverBase { }; } - let [moduleName, subPath] = this.getModuleParts(id); + let [moduleName, subPath] = getModuleParts(id); let dir = path.dirname(sourceFile); let moduleDir = this.fs.findNodeModule(moduleName, dir); diff --git a/packages/core/plugin/src/PluginAPI.js b/packages/core/plugin/src/PluginAPI.js index 07d7ea1a0e5..d0bcd49f7f0 100644 --- a/packages/core/plugin/src/PluginAPI.js +++ b/packages/core/plugin/src/PluginAPI.js @@ -58,14 +58,14 @@ export class Validator { } export class Packager { - constructor(opts: PackagerOpts) { + constructor(opts: PackagerOpts) { // $FlowFixMe this[CONFIG] = opts; } } export class Optimizer { - constructor(opts: OptimizerOpts) { + constructor(opts: OptimizerOpts) { // $FlowFixMe this[CONFIG] = opts; } diff --git a/packages/core/types/index.js b/packages/core/types/index.js index 29aa68ee9fd..a9153efb6de 100644 --- a/packages/core/types/index.js +++ b/packages/core/types/index.js @@ -808,8 +808,10 @@ export interface Config { invalidateOnFileCreate(FileCreateInvalidation): void; /** Invalidates the config when the given environment variable changes. */ invalidateOnEnvChange(string): void; - /** Invalidates the config when Parcel restarts. */ + /** Invalidates the config only when Parcel restarts. */ invalidateOnStartup(): void; + /** Invalidates the config on every build. */ + invalidateOnBuild(): void; /** * Adds a dev dependency to the config. If the dev dependency or any of its * dependencies change, the config will be invalidated. @@ -1591,18 +1593,26 @@ export type Runtime = {| /** * @section packager */ -export type Packager = {| +export type Packager = {| loadConfig?: ({| config: Config, options: PluginOptions, logger: PluginLogger, - |}) => Promise | ConfigType, + |}) => Async, + loadBundleConfig?: ({| + bundle: NamedBundle, + bundleGraph: BundleGraph, + config: Config, + options: PluginOptions, + logger: PluginLogger, + |}) => Async, package({| bundle: NamedBundle, bundleGraph: BundleGraph, options: PluginOptions, logger: PluginLogger, config: ConfigType, + bundleConfig: BundleConfigType, getInlineBundleContents: ( Bundle, BundleGraph, @@ -1614,12 +1624,19 @@ export type Packager = {| /** * @section optimizer */ -export type Optimizer = {| +export type Optimizer = {| loadConfig?: ({| config: Config, options: PluginOptions, logger: PluginLogger, - |}) => Promise | ConfigType, + |}) => Async, + loadBundleConfig?: ({| + bundle: NamedBundle, + bundleGraph: BundleGraph, + config: Config, + options: PluginOptions, + logger: PluginLogger, + |}) => Async, optimize({| bundle: NamedBundle, bundleGraph: BundleGraph, @@ -1628,6 +1645,7 @@ export type Optimizer = {| options: PluginOptions, logger: PluginLogger, config: ConfigType, + bundleConfig: BundleConfigType, getSourceMapReference: (map: ?SourceMap) => Async, |}): Async, |}; diff --git a/packages/core/utils/src/getModuleParts.js b/packages/core/utils/src/getModuleParts.js new file mode 100644 index 00000000000..41308ead1fd --- /dev/null +++ b/packages/core/utils/src/getModuleParts.js @@ -0,0 +1,23 @@ +// @flow strict-local +import path from 'path'; + +import {normalizeSeparators} from './path'; + +/** + * Returns the package name and the optional subpath + */ +export default function getModuleParts(_name: string): [string, ?string] { + let name = path.normalize(_name); + let splitOn = name.indexOf(path.sep); + if (name.charAt(0) === '@') { + splitOn = name.indexOf(path.sep, splitOn + 1); + } + if (splitOn < 0) { + return [normalizeSeparators(name), undefined]; + } else { + return [ + normalizeSeparators(name.substring(0, splitOn)), + name.substring(splitOn + 1) || undefined, + ]; + } +} diff --git a/packages/core/utils/src/index.js b/packages/core/utils/src/index.js index 2dbc483c826..63bf339e0ab 100644 --- a/packages/core/utils/src/index.js +++ b/packages/core/utils/src/index.js @@ -11,6 +11,7 @@ export {default as countLines} from './countLines'; export {default as generateBuildMetrics} from './generateBuildMetrics'; export {default as generateCertificate} from './generateCertificate'; export {default as getCertificate} from './getCertificate'; +export {default as getModuleParts} from './getModuleParts'; export {default as getRootDir} from './getRootDir'; export {default as isDirectoryInside} from './isDirectoryInside'; export {default as isURL} from './is-url'; diff --git a/packages/resolvers/default/src/DefaultResolver.js b/packages/resolvers/default/src/DefaultResolver.js index 835c881d4d1..6b166af04df 100644 --- a/packages/resolvers/default/src/DefaultResolver.js +++ b/packages/resolvers/default/src/DefaultResolver.js @@ -25,9 +25,8 @@ export default (new Resolver({ ? ['ts', 'tsx', 'js', 'jsx', 'json'] : [], mainFields: ['source', 'browser', 'module', 'main'], - packageManager: options.shouldAutoInstall - ? options.packageManager - : undefined, + packageManager: options.packageManager, + shouldAutoInstall: options.shouldAutoInstall, logger, }); diff --git a/packages/transformers/js/core/Cargo.toml b/packages/transformers/js/core/Cargo.toml index d3b7d21d688..a758e450a21 100644 --- a/packages/transformers/js/core/Cargo.toml +++ b/packages/transformers/js/core/Cargo.toml @@ -8,9 +8,9 @@ edition = "2018" crate-type = ["rlib"] [dependencies] -swc_ecmascript = { version = "0.179.0", features = ["parser", "transforms", "module", "optimization", "react", "typescript", "utils", "visit", "codegen", "utils", "preset_env"] } -swc_common = { version = "0.23.0", features = ["tty-emitter", "sourcemap"] } -swc_atoms = "0.2.13" +swc_ecmascript = { version = "0.192.0", features = ["parser", "transforms", "module", "optimization", "react", "typescript", "utils", "visit", "codegen", "utils", "preset_env"] } +swc_common = { version = "0.27.13", features = ["tty-emitter", "sourcemap"] } +swc_atoms = "0.4.8" indoc = "1.0.3" serde = "1.0.123" serde_bytes = "0.11.5" diff --git a/packages/transformers/js/core/src/utils.rs b/packages/transformers/js/core/src/utils.rs index a6f2a6d859d..667f036eba1 100644 --- a/packages/transformers/js/core/src/utils.rs +++ b/packages/transformers/js/core/src/utils.rs @@ -85,7 +85,7 @@ pub fn match_str(node: &ast::Expr) -> Option<(JsWord, Span)> { Expr::Lit(Lit::Str(s)) => Some((s.value.clone(), s.span)), // `string` Expr::Tpl(tpl) if tpl.quasis.len() == 1 && tpl.exprs.is_empty() => { - Some((tpl.quasis[0].raw.clone(), tpl.span)) + Some(((*tpl.quasis[0].raw).into(), tpl.span)) } _ => None, } diff --git a/packages/utils/node-resolver-core/src/NodeResolver.js b/packages/utils/node-resolver-core/src/NodeResolver.js index 9e89cd02177..51663daaddb 100644 --- a/packages/utils/node-resolver-core/src/NodeResolver.js +++ b/packages/utils/node-resolver-core/src/NodeResolver.js @@ -22,6 +22,7 @@ import { findAlternativeNodeModules, findAlternativeFiles, loadConfig, + getModuleParts, globToRegex, isGlobMatch, } from '@parcel/utils'; @@ -46,6 +47,7 @@ type Options = {| mainFields: Array, packageManager?: PackageManager, logger?: PluginLogger, + shouldAutoInstall?: boolean, |}; type ResolvedFile = {| path: string, @@ -96,6 +98,7 @@ export default class NodeResolver { packageCache: Map; rootPackage: InternalPackageJSON | null; packageManager: ?PackageManager; + shouldAutoInstall: boolean; logger: ?PluginLogger; constructor(opts: Options) { @@ -108,6 +111,7 @@ export default class NodeResolver { this.packageCache = new Map(); this.rootPackage = null; this.packageManager = opts.packageManager; + this.shouldAutoInstall = opts.shouldAutoInstall ?? false; this.logger = opts.logger; } @@ -271,10 +275,10 @@ export default class NodeResolver { let builtin = this.findBuiltin(filename, env); if (builtin === null) { return null; - } else if (builtin === empty) { + } else if (builtin && builtin.name === empty) { return {filePath: empty}; } else if (builtin !== undefined) { - filename = builtin; + filename = builtin.name; } if (this.shouldIncludeNodeModule(env, filename) === false) { @@ -287,51 +291,26 @@ export default class NodeResolver { // Resolve the module in node_modules let resolved: ?Module; try { - resolved = this.findNodeModulePath(filename, sourceFile, ctx); + resolved = this.findNodeModulePath( + filename, + !builtin ? sourceFile : path.join(this.projectRoot, 'index'), + ctx, + ); } catch (err) { // ignore } - // Auto install node builtin polyfills if not already available - if (resolved === undefined && builtin != null) { - let packageName = builtin.split('/')[0]; + // Autoinstall/verify version of builtin polyfills + if (builtin?.range != null) { + // This assumes that there are no polyfill packages that are scoped + // Append '/' to force this.packageManager to look up the package in node_modules + let packageName = builtin.name.split('/')[0] + '/'; let packageManager = this.packageManager; - if (packageManager) { - this.logger?.warn({ - message: md`Auto installing polyfill for Node builtin module "${specifier}"...`, - codeFrames: [ - { - filePath: ctx.loc?.filePath ?? sourceFile, - codeHighlights: ctx.loc - ? [ - { - message: 'used here', - start: ctx.loc.start, - end: ctx.loc.end, - }, - ] - : [], - }, - ], - documentationURL: - 'https://parceljs.org/features/node-emulation/#polyfilling-%26-excluding-builtin-node-modules', - }); - - await packageManager.resolve(builtin, this.projectRoot + '/index', { - saveDev: true, - shouldAutoInstall: true, - }); - - // Re-resolve - try { - resolved = this.findNodeModulePath(filename, sourceFile, ctx); - } catch (err) { - // ignore - } - } else { - throw new ThrowableDiagnostic({ - diagnostic: { - message: md`Node builtin polyfill "${packageName}" is not installed, but auto install is disabled.`, + if (resolved == null) { + // Auto install the Node builtin polyfills + if (this.shouldAutoInstall && packageManager) { + this.logger?.warn({ + message: md`Auto installing polyfill for Node builtin module "${specifier}"...`, codeFrames: [ { filePath: ctx.loc?.filePath ?? sourceFile, @@ -348,17 +327,74 @@ export default class NodeResolver { ], documentationURL: 'https://parceljs.org/features/node-emulation/#polyfilling-%26-excluding-builtin-node-modules', - hints: [ - md`Install the "${packageName}" package with your package manager, and run Parcel again.`, - ], + }); + + await packageManager.resolve( + packageName, + this.projectRoot + '/index', + { + saveDev: true, + shouldAutoInstall: true, + range: builtin.range, + }, + ); + + // Re-resolve + try { + resolved = this.findNodeModulePath( + filename, + this.projectRoot + '/index', + ctx, + ); + } catch (err) { + // ignore + } + } else { + throw new ThrowableDiagnostic({ + diagnostic: { + message: md`Node builtin polyfill "${packageName}" is not installed, but auto install is disabled.`, + codeFrames: [ + { + filePath: ctx.loc?.filePath ?? sourceFile, + codeHighlights: ctx.loc + ? [ + { + message: 'used here', + start: ctx.loc.start, + end: ctx.loc.end, + }, + ] + : [], + }, + ], + documentationURL: + 'https://parceljs.org/features/node-emulation/#polyfilling-%26-excluding-builtin-node-modules', + hints: [ + md`Install the "${packageName}" package with your package manager, and run Parcel again.`, + ], + }, + }); + } + } else if (builtin.range != null) { + // Assert correct version + + // TODO packageManager can be null for backwards compatibility, but that could cause invalid + // resolutions in monorepos + await packageManager?.resolve( + packageName, + this.projectRoot + '/index', + { + saveDev: true, + shouldAutoInstall: this.shouldAutoInstall, + range: builtin.range, }, - }); + ); } } if (resolved === undefined && process.versions.pnp != null && parent) { try { - let [moduleName, subPath] = this.getModuleParts(filename); + let [moduleName, subPath] = getModuleParts(filename); // $FlowFixMe[prop-missing] let pnp = _Module.findPnpApi(path.dirname(parent)); @@ -389,7 +425,7 @@ export default class NodeResolver { // If we couldn't resolve the node_modules path, just return the module name info if (resolved === undefined) { - let [moduleName, subPath] = this.getModuleParts(filename); + let [moduleName, subPath] = getModuleParts(filename); resolved = ({ moduleName, subPath, @@ -429,12 +465,12 @@ export default class NodeResolver { } if (Array.isArray(includeNodeModules)) { - let [moduleName] = this.getModuleParts(name); + let [moduleName] = getModuleParts(name); return includeNodeModules.includes(moduleName); } if (includeNodeModules && typeof includeNodeModules === 'object') { - let [moduleName] = this.getModuleParts(name); + let [moduleName] = getModuleParts(name); let include = includeNodeModules[moduleName]; if (include != null) { return !!include; @@ -447,7 +483,7 @@ export default class NodeResolver { name: string, ctx: ResolverContext, ) { - let [moduleName] = this.getModuleParts(name); + let [moduleName] = getModuleParts(name); let pkg = await this.findPackage(sourceFile, ctx); if (!pkg) { return; @@ -707,7 +743,10 @@ export default class NodeResolver { return resolvedFile; } - findBuiltin(filename: string, env: Environment): ?string { + findBuiltin( + filename: string, + env: Environment, + ): ?{|name: string, range: ?string|} { const isExplicitNode = filename.startsWith('node:'); if (isExplicitNode || builtins[filename]) { if (env.isNode()) { @@ -739,7 +778,7 @@ export default class NodeResolver { sourceFile: FilePath, ctx: ResolverContext, ): ?Module { - let [moduleName, subPath] = this.getModuleParts(filename); + let [moduleName, subPath] = getModuleParts(filename); ctx.invalidateOnFileCreate.push({ fileName: `node_modules/${moduleName}`, @@ -1150,7 +1189,7 @@ export default class NodeResolver { alias = this.lookupAlias(aliases, normalizeSeparators(filename)); if (alias == null) { // If it didn't match, try only the module name. - let [moduleName, subPath] = this.getModuleParts(filename); + let [moduleName, subPath] = getModuleParts(filename); alias = this.lookupAlias(aliases, moduleName); if (typeof alias === 'string' && subPath) { let isRelative = alias.startsWith('./'); @@ -1288,22 +1327,6 @@ export default class NodeResolver { return this.resolveAliases(filename, env, pkg); } - getModuleParts(name: string): [FilePath, ?string] { - name = path.normalize(name); - let splitOn = name.indexOf(path.sep); - if (name.charAt(0) === '@') { - splitOn = name.indexOf(path.sep, splitOn + 1); - } - if (splitOn < 0) { - return [normalizeSeparators(name), undefined]; - } else { - return [ - normalizeSeparators(name.substring(0, splitOn)), - name.substring(splitOn + 1) || undefined, - ]; - } - } - hasSideEffects(filePath: FilePath, pkg: InternalPackageJSON): boolean { switch (typeof pkg.sideEffects) { case 'boolean': diff --git a/packages/utils/node-resolver-core/src/builtins.js b/packages/utils/node-resolver-core/src/builtins.js index d733e527811..2abcafc6a3f 100644 --- a/packages/utils/node-resolver-core/src/builtins.js +++ b/packages/utils/node-resolver-core/src/builtins.js @@ -1,38 +1,53 @@ // @flow strict-local // $FlowFixMe this is untyped import {builtinModules} from 'module'; +import nullthrows from 'nullthrows'; +// flowlint-next-line untyped-import:off +import packageJson from '../package.json'; export const empty: string = require.resolve('./_empty.js'); -// $FlowFixMe -let builtins: {[string]: any, ...} = Object.create(null); +let builtins: {[string]: {|name: string, range: ?string|}, ...} = + // $FlowFixMe + Object.create(null); + // use definite (current) list of Node builtins for (let key of builtinModules) { - builtins[key] = empty; + builtins[key] = {name: empty, range: null}; } -builtins.assert = 'assert/'; -builtins.buffer = 'buffer/'; -builtins.console = 'console-browserify'; -builtins.constants = 'constants-browserify'; -builtins.crypto = 'crypto-browserify'; -builtins.domain = 'domain-browser'; -builtins.events = 'events/'; -builtins.http = 'stream-http'; -builtins.https = 'https-browserify'; -builtins.os = 'os-browserify/browser.js'; -builtins.path = 'path-browserify'; -builtins.process = 'process/browser.js'; -builtins.punycode = 'punycode/'; -builtins.querystring = 'querystring-es3/'; -builtins.stream = 'stream-browserify'; -builtins.string_decoder = 'string_decoder/'; -builtins.sys = 'util/util.js'; -builtins.timers = 'timers-browserify'; -builtins.tty = 'tty-browserify'; -builtins.url = 'url/'; -builtins.util = 'util/util.js'; -builtins.vm = 'vm-browserify'; -builtins.zlib = 'browserify-zlib'; +let polyfills = { + assert: 'assert', + buffer: 'buffer', + console: 'console-browserify', + constants: 'constants-browserify', + crypto: 'crypto-browserify', + domain: 'domain-browser', + events: 'events', + http: 'stream-http', + https: 'https-browserify', + os: 'os-browserify', + path: 'path-browserify', + process: 'process', + punycode: 'punycode', + querystring: 'querystring-es3', + stream: 'stream-browserify', + string_decoder: 'string_decoder', + sys: 'util', + timers: 'timers-browserify', + tty: 'tty-browserify', + url: 'url', + util: 'util', + vm: 'vm-browserify', + zlib: 'browserify-zlib', +}; + +for (let k in polyfills) { + let polyfill = polyfills[k]; + builtins[k] = { + name: polyfill + (builtinModules.includes(polyfill) ? '/' : ''), + range: nullthrows(packageJson.devDependencies[polyfill]), + }; +} export default builtins; diff --git a/packages/utils/node-resolver-core/test/resolver.js b/packages/utils/node-resolver-core/test/resolver.js index e28f9bf5f7f..9b130fc9fbe 100644 --- a/packages/utils/node-resolver-core/test/resolver.js +++ b/packages/utils/node-resolver-core/test/resolver.js @@ -305,7 +305,7 @@ describe('resolver', function () { }, { fileName: 'node_modules/browserify-zlib', - aboveFilePath: path.join(rootDir, 'foo.js'), + aboveFilePath: path.join(rootDir, 'index'), }, { fileName: 'package.json', @@ -341,7 +341,7 @@ describe('resolver', function () { }, { fileName: 'node_modules/browserify-zlib', - aboveFilePath: path.join(rootDir, 'foo.js'), + aboveFilePath: path.join(rootDir, 'index'), }, { fileName: 'package.json',