From 7e69ba03853d8f0633ede460aa5296dd80ae613e Mon Sep 17 00:00:00 2001 From: Tony Hallett Date: Wed, 18 May 2022 17:26:46 +0100 Subject: [PATCH 1/5] add invalidateOnEnvChange with unit tests --- .../core/core/src/requests/PathRequest.js | 16 ++ packages/core/core/test/PathRequest.test.js | 228 ++++++++++++++++++ packages/core/types/index.js | 2 + 3 files changed, 246 insertions(+) create mode 100644 packages/core/core/test/PathRequest.test.js diff --git a/packages/core/core/src/requests/PathRequest.js b/packages/core/core/src/requests/PathRequest.js index 347fbe12468..6bb95cc21be 100644 --- a/packages/core/core/src/requests/PathRequest.js +++ b/packages/core/core/src/requests/PathRequest.js @@ -67,6 +67,12 @@ async function run({input, api, options}: RunOpts) { }); let result: ResolverResult = await resolverRunner.resolve(input.dependency); + if (result.invalidateOnEnvChange) { + for (let env of result.invalidateOnEnvChange) { + api.invalidateOnEnvChange(env); + } + } + if (result.invalidateOnFileCreate) { for (let file of result.invalidateOnFileCreate) { api.invalidateOnFileCreate( @@ -105,6 +111,7 @@ type ResolverResult = {| assetGroup: ?AssetGroup, invalidateOnFileCreate?: Array, invalidateOnFileChange?: Array, + invalidateOnEnvChange?: Array, diagnostics?: Array, |}; @@ -185,6 +192,7 @@ export class ResolverRunner { let diagnostics: Array = []; let invalidateOnFileCreate = []; let invalidateOnFileChange = []; + let invalidateOnEnvChange = []; for (let resolver of resolvers) { try { let result = await resolver.plugin.resolve({ @@ -208,6 +216,10 @@ export class ResolverRunner { dependency.priority = Priority[result.priority]; } + if (result.invalidateOnEnvChange) { + invalidateOnEnvChange.push(...result.invalidateOnEnvChange); + } + if (result.invalidateOnFileCreate) { invalidateOnFileCreate.push(...result.invalidateOnFileCreate); } @@ -221,6 +233,7 @@ export class ResolverRunner { assetGroup: null, invalidateOnFileCreate, invalidateOnFileChange, + invalidateOnEnvChange, }; } @@ -251,6 +264,7 @@ export class ResolverRunner { }, invalidateOnFileCreate, invalidateOnFileChange, + invalidateOnEnvChange, }; } @@ -286,6 +300,7 @@ export class ResolverRunner { assetGroup: null, invalidateOnFileCreate, invalidateOnFileChange, + invalidateOnEnvChange, }; } @@ -308,6 +323,7 @@ export class ResolverRunner { assetGroup: null, invalidateOnFileCreate, invalidateOnFileChange, + invalidateOnEnvChange, diagnostics, }; } diff --git a/packages/core/core/test/PathRequest.test.js b/packages/core/core/test/PathRequest.test.js new file mode 100644 index 00000000000..8155b3d54e6 --- /dev/null +++ b/packages/core/core/test/PathRequest.test.js @@ -0,0 +1,228 @@ +import createPathRequest from '../src/requests/PathRequest'; +import {getCachedParcelConfig} from '../src/requests/ParcelConfigRequest'; +import {clearBuildCaches} from '../src/buildCache'; +import sinon from 'sinon'; +import assert from 'assert'; + +function createMockedParcelConfig(resolverPlugins, options, config = {}) { + const configResult = { + config, + cachePath: '_cachepath', + }; + const resolvers = resolverPlugins.map(resolver => { + return { + plugin: resolver, + }; + }); + const parcelConfig = getCachedParcelConfig(configResult, options); + parcelConfig.getResolvers = () => Promise.resolve(resolvers); + return configResult; +} + +function createApi(configResult) { + return { + runRequest: () => Promise.resolve(configResult), + invalidateOnEnvChange: sinon.spy(), + invalidateOnFileCreate: sinon.spy(), + invalidateOnFileUpdate: sinon.spy(), + invalidateOnFileDelete: sinon.spy(), + }; +} + +function setUp(resolverPlugins, options, config = {}) { + const configResult = createMockedParcelConfig( + resolverPlugins, + options, + config, + ); + return createApi(configResult); +} + +function runPathRequest(runOpts) { + const pathRequest = createPathRequest({ + name: '', + dependency: { + id: '', + }, + }); + + return pathRequest.run(runOpts); +} + +describe('PathRequest', () => { + afterEach(() => { + clearBuildCaches(); + }); + + describe('should api.invalidateOnEnvChange', () => { + it('when a resolving plugin resolves', async () => { + const projectRoot = 'C://ProjectRoot'; + const filePath = `${projectRoot}/resolved.js`; // needs to be absolute + const options = { + projectRoot, + }; + + const resolverPlugins = [ + { + name: 'resolving plugin', + resolve: () => + Promise.resolve({ + filePath, + pipeline: '', + invalidateOnEnvChange: ['first', 'second'], + }), + }, + ]; + + const api = setUp(resolverPlugins, options); + + await runPathRequest({ + input: { + dependency: { + specifier: '', + resolveFrom: '', + }, + }, + api, + options, + }); + + // act + assert(api.invalidateOnEnvChange.callCount == 2); + assert(api.invalidateOnEnvChange.calledWith('first')); + assert(api.invalidateOnEnvChange.calledWith('second')); + }); + + it('when no resolution and the result is excluded', async () => { + const projectRoot = 'C://ProjectRoot'; + const options = { + projectRoot, + }; + + const resolverPlugins = [ + { + name: 'invalidating plugin 1', + resolve: () => + Promise.resolve({ + invalidateOnEnvChange: ['plugin1'], + }), + }, + { + name: 'invalidating plugin 2', + resolve: () => + Promise.resolve({ + invalidateOnEnvChange: ['plugin2'], + isExcluded: true, + }), + }, + ]; + + const api = setUp(resolverPlugins, options); + + await runPathRequest({ + input: { + dependency: { + specifier: '', + resolveFrom: '', + }, + }, + api, + options, + }); + + // act + assert(api.invalidateOnEnvChange.callCount == 2); + assert(api.invalidateOnEnvChange.calledWith('plugin1')); + assert(api.invalidateOnEnvChange.calledWith('plugin2')); + }); + + it('when no resolution and the dependency is optional', async () => { + const projectRoot = 'C://ProjectRoot'; + const options = { + projectRoot, + }; + + const resolverPlugins = [ + { + name: 'invalidating plugin 1', + resolve: () => + Promise.resolve({ + invalidateOnEnvChange: ['plugin1'], + }), + }, + { + name: 'invalidating plugin 2', + resolve: () => + Promise.resolve({ + invalidateOnEnvChange: ['plugin2'], + }), + }, + ]; + + const api = setUp(resolverPlugins, options); + + await runPathRequest({ + input: { + dependency: { + isOptional: true, + specifier: '', + resolveFrom: '', + }, + }, + api, + options, + }); + + // act + assert(api.invalidateOnEnvChange.callCount == 2); + assert(api.invalidateOnEnvChange.calledWith('plugin1')); + assert(api.invalidateOnEnvChange.calledWith('plugin2')); + }); + + it('when no resolution and the dependency is not optional', async () => { + const projectRoot = 'C://ProjectRoot'; + const options = { + projectRoot, + }; + + const resolverPlugins = [ + { + name: 'invalidating plugin 1', + resolve: () => + Promise.resolve({ + invalidateOnEnvChange: ['plugin1'], + }), + }, + { + name: 'invalidating plugin 2', + resolve: () => + Promise.resolve({ + invalidateOnEnvChange: ['plugin2'], + }), + isExcluded: true, + }, + ]; + + const api = setUp(resolverPlugins, options); + try { + await runPathRequest({ + input: { + dependency: { + specifier: '', + sourcePath: null, + }, + }, + api, + options, + }); + } catch { + //expected exception + } + + // act + assert(api.invalidateOnEnvChange.callCount == 2); + assert(api.invalidateOnEnvChange.calledWith('plugin1')); + assert(api.invalidateOnEnvChange.calledWith('plugin2')); + }); + }); +}); diff --git a/packages/core/types/index.js b/packages/core/types/index.js index 57c29b0c5ee..6ca07f9001d 100644 --- a/packages/core/types/index.js +++ b/packages/core/types/index.js @@ -1499,6 +1499,8 @@ export type ResolveResult = {| +invalidateOnFileCreate?: Array, /** A list of files that should invalidate the resolution if modified or deleted. */ +invalidateOnFileChange?: Array, + /** Invalidates the resolution when the given environment variable changes.*/ + +invalidateOnEnvChange?: Array, |}; /** From 77ebcd6409d9fbcd3b2f88ecda148fbf04289d02 Mon Sep 17 00:00:00 2001 From: Tony Hallett Date: Wed, 18 May 2022 18:18:25 +0100 Subject: [PATCH 2/5] remove unnecessary field --- packages/core/core/test/PathRequest.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/core/core/test/PathRequest.test.js b/packages/core/core/test/PathRequest.test.js index 8155b3d54e6..5a6e0fe3a5f 100644 --- a/packages/core/core/test/PathRequest.test.js +++ b/packages/core/core/test/PathRequest.test.js @@ -199,7 +199,6 @@ describe('PathRequest', () => { Promise.resolve({ invalidateOnEnvChange: ['plugin2'], }), - isExcluded: true, }, ]; From b68aa3401da4551f7497c11e2c19536475d80e73 Mon Sep 17 00:00:00 2001 From: Tony Hallett Date: Wed, 18 May 2022 21:40:14 +0100 Subject: [PATCH 3/5] remove old comments --- packages/core/core/test/PathRequest.test.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/core/core/test/PathRequest.test.js b/packages/core/core/test/PathRequest.test.js index 5a6e0fe3a5f..b78a51b58e5 100644 --- a/packages/core/core/test/PathRequest.test.js +++ b/packages/core/core/test/PathRequest.test.js @@ -87,7 +87,6 @@ describe('PathRequest', () => { options, }); - // act assert(api.invalidateOnEnvChange.callCount == 2); assert(api.invalidateOnEnvChange.calledWith('first')); assert(api.invalidateOnEnvChange.calledWith('second')); @@ -130,7 +129,6 @@ describe('PathRequest', () => { options, }); - // act assert(api.invalidateOnEnvChange.callCount == 2); assert(api.invalidateOnEnvChange.calledWith('plugin1')); assert(api.invalidateOnEnvChange.calledWith('plugin2')); @@ -173,7 +171,6 @@ describe('PathRequest', () => { options, }); - // act assert(api.invalidateOnEnvChange.callCount == 2); assert(api.invalidateOnEnvChange.calledWith('plugin1')); assert(api.invalidateOnEnvChange.calledWith('plugin2')); @@ -218,7 +215,6 @@ describe('PathRequest', () => { //expected exception } - // act assert(api.invalidateOnEnvChange.callCount == 2); assert(api.invalidateOnEnvChange.calledWith('plugin1')); assert(api.invalidateOnEnvChange.calledWith('plugin2')); From a1d76d61cba9858f761c96c7b2defaf2b8a4d4cb Mon Sep 17 00:00:00 2001 From: Tony Hallett Date: Sat, 21 May 2022 09:31:41 +0100 Subject: [PATCH 4/5] switch to an integration test --- packages/core/core/test/PathRequest.test.js | 223 ------------------ .../.parcelrc | 4 + .../index.js | 1 + .../index.js | 27 +++ .../package.json | 7 + .../package.json | 4 + .../core/integration-tests/test/plugin.js | 21 ++ 7 files changed, 64 insertions(+), 223 deletions(-) delete mode 100644 packages/core/core/test/PathRequest.test.js create mode 100644 packages/core/integration-tests/test/integration/resolver-can-invalidateonenvchange/.parcelrc create mode 100644 packages/core/integration-tests/test/integration/resolver-can-invalidateonenvchange/index.js create mode 100644 packages/core/integration-tests/test/integration/resolver-can-invalidateonenvchange/node_modules/parcel-resolver-can-invalidateonenvchange/index.js create mode 100644 packages/core/integration-tests/test/integration/resolver-can-invalidateonenvchange/node_modules/parcel-resolver-can-invalidateonenvchange/package.json create mode 100644 packages/core/integration-tests/test/integration/resolver-can-invalidateonenvchange/package.json diff --git a/packages/core/core/test/PathRequest.test.js b/packages/core/core/test/PathRequest.test.js deleted file mode 100644 index b78a51b58e5..00000000000 --- a/packages/core/core/test/PathRequest.test.js +++ /dev/null @@ -1,223 +0,0 @@ -import createPathRequest from '../src/requests/PathRequest'; -import {getCachedParcelConfig} from '../src/requests/ParcelConfigRequest'; -import {clearBuildCaches} from '../src/buildCache'; -import sinon from 'sinon'; -import assert from 'assert'; - -function createMockedParcelConfig(resolverPlugins, options, config = {}) { - const configResult = { - config, - cachePath: '_cachepath', - }; - const resolvers = resolverPlugins.map(resolver => { - return { - plugin: resolver, - }; - }); - const parcelConfig = getCachedParcelConfig(configResult, options); - parcelConfig.getResolvers = () => Promise.resolve(resolvers); - return configResult; -} - -function createApi(configResult) { - return { - runRequest: () => Promise.resolve(configResult), - invalidateOnEnvChange: sinon.spy(), - invalidateOnFileCreate: sinon.spy(), - invalidateOnFileUpdate: sinon.spy(), - invalidateOnFileDelete: sinon.spy(), - }; -} - -function setUp(resolverPlugins, options, config = {}) { - const configResult = createMockedParcelConfig( - resolverPlugins, - options, - config, - ); - return createApi(configResult); -} - -function runPathRequest(runOpts) { - const pathRequest = createPathRequest({ - name: '', - dependency: { - id: '', - }, - }); - - return pathRequest.run(runOpts); -} - -describe('PathRequest', () => { - afterEach(() => { - clearBuildCaches(); - }); - - describe('should api.invalidateOnEnvChange', () => { - it('when a resolving plugin resolves', async () => { - const projectRoot = 'C://ProjectRoot'; - const filePath = `${projectRoot}/resolved.js`; // needs to be absolute - const options = { - projectRoot, - }; - - const resolverPlugins = [ - { - name: 'resolving plugin', - resolve: () => - Promise.resolve({ - filePath, - pipeline: '', - invalidateOnEnvChange: ['first', 'second'], - }), - }, - ]; - - const api = setUp(resolverPlugins, options); - - await runPathRequest({ - input: { - dependency: { - specifier: '', - resolveFrom: '', - }, - }, - api, - options, - }); - - assert(api.invalidateOnEnvChange.callCount == 2); - assert(api.invalidateOnEnvChange.calledWith('first')); - assert(api.invalidateOnEnvChange.calledWith('second')); - }); - - it('when no resolution and the result is excluded', async () => { - const projectRoot = 'C://ProjectRoot'; - const options = { - projectRoot, - }; - - const resolverPlugins = [ - { - name: 'invalidating plugin 1', - resolve: () => - Promise.resolve({ - invalidateOnEnvChange: ['plugin1'], - }), - }, - { - name: 'invalidating plugin 2', - resolve: () => - Promise.resolve({ - invalidateOnEnvChange: ['plugin2'], - isExcluded: true, - }), - }, - ]; - - const api = setUp(resolverPlugins, options); - - await runPathRequest({ - input: { - dependency: { - specifier: '', - resolveFrom: '', - }, - }, - api, - options, - }); - - assert(api.invalidateOnEnvChange.callCount == 2); - assert(api.invalidateOnEnvChange.calledWith('plugin1')); - assert(api.invalidateOnEnvChange.calledWith('plugin2')); - }); - - it('when no resolution and the dependency is optional', async () => { - const projectRoot = 'C://ProjectRoot'; - const options = { - projectRoot, - }; - - const resolverPlugins = [ - { - name: 'invalidating plugin 1', - resolve: () => - Promise.resolve({ - invalidateOnEnvChange: ['plugin1'], - }), - }, - { - name: 'invalidating plugin 2', - resolve: () => - Promise.resolve({ - invalidateOnEnvChange: ['plugin2'], - }), - }, - ]; - - const api = setUp(resolverPlugins, options); - - await runPathRequest({ - input: { - dependency: { - isOptional: true, - specifier: '', - resolveFrom: '', - }, - }, - api, - options, - }); - - assert(api.invalidateOnEnvChange.callCount == 2); - assert(api.invalidateOnEnvChange.calledWith('plugin1')); - assert(api.invalidateOnEnvChange.calledWith('plugin2')); - }); - - it('when no resolution and the dependency is not optional', async () => { - const projectRoot = 'C://ProjectRoot'; - const options = { - projectRoot, - }; - - const resolverPlugins = [ - { - name: 'invalidating plugin 1', - resolve: () => - Promise.resolve({ - invalidateOnEnvChange: ['plugin1'], - }), - }, - { - name: 'invalidating plugin 2', - resolve: () => - Promise.resolve({ - invalidateOnEnvChange: ['plugin2'], - }), - }, - ]; - - const api = setUp(resolverPlugins, options); - try { - await runPathRequest({ - input: { - dependency: { - specifier: '', - sourcePath: null, - }, - }, - api, - options, - }); - } catch { - //expected exception - } - - assert(api.invalidateOnEnvChange.callCount == 2); - assert(api.invalidateOnEnvChange.calledWith('plugin1')); - assert(api.invalidateOnEnvChange.calledWith('plugin2')); - }); - }); -}); diff --git a/packages/core/integration-tests/test/integration/resolver-can-invalidateonenvchange/.parcelrc b/packages/core/integration-tests/test/integration/resolver-can-invalidateonenvchange/.parcelrc new file mode 100644 index 00000000000..6e7fa4a9bde --- /dev/null +++ b/packages/core/integration-tests/test/integration/resolver-can-invalidateonenvchange/.parcelrc @@ -0,0 +1,4 @@ +{ + "extends": "@parcel/config-default", + "resolvers": ["parcel-resolver-can-invalidateonenvchange"] +} diff --git a/packages/core/integration-tests/test/integration/resolver-can-invalidateonenvchange/index.js b/packages/core/integration-tests/test/integration/resolver-can-invalidateonenvchange/index.js new file mode 100644 index 00000000000..69b7c3258ab --- /dev/null +++ b/packages/core/integration-tests/test/integration/resolver-can-invalidateonenvchange/index.js @@ -0,0 +1 @@ +const willBeReplaced = true; \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/resolver-can-invalidateonenvchange/node_modules/parcel-resolver-can-invalidateonenvchange/index.js b/packages/core/integration-tests/test/integration/resolver-can-invalidateonenvchange/node_modules/parcel-resolver-can-invalidateonenvchange/index.js new file mode 100644 index 00000000000..ade8fcd2fd7 --- /dev/null +++ b/packages/core/integration-tests/test/integration/resolver-can-invalidateonenvchange/node_modules/parcel-resolver-can-invalidateonenvchange/index.js @@ -0,0 +1,27 @@ +// @flow + +const {Resolver} = require('@parcel/plugin'); +const path = require('path'); +const {default: NodeResolver} = require('@parcel/node-resolver-core'); + +module.exports = new Resolver({ + async resolve({dependency, options, specifier,logger}) { + let mainFields = ['source', 'browser', 'module', 'main']; + const replacedCode = options.env.replacedCode; + const resolver = new NodeResolver({ + fs: options.inputFS, + projectRoot: options.projectRoot, + extensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'css', 'styl', 'vue'], + mainFields, + }); + let result = await resolver.resolve({ + filename: specifier, + specifierType: dependency.specifierType, + parent: dependency.sourcePath, + env: dependency.env, + }); + result.code = replacedCode; + result.invalidateOnEnvChange = ['replacedCode']; + return result; + }, +}); diff --git a/packages/core/integration-tests/test/integration/resolver-can-invalidateonenvchange/node_modules/parcel-resolver-can-invalidateonenvchange/package.json b/packages/core/integration-tests/test/integration/resolver-can-invalidateonenvchange/node_modules/parcel-resolver-can-invalidateonenvchange/package.json new file mode 100644 index 00000000000..005c6096b8f --- /dev/null +++ b/packages/core/integration-tests/test/integration/resolver-can-invalidateonenvchange/node_modules/parcel-resolver-can-invalidateonenvchange/package.json @@ -0,0 +1,7 @@ +{ + "name": "parcel-resolver-can-invalidateonenvchange", + "version": "1.0.0", + "private": true, + "main": "index.js" + } + \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/resolver-can-invalidateonenvchange/package.json b/packages/core/integration-tests/test/integration/resolver-can-invalidateonenvchange/package.json new file mode 100644 index 00000000000..65d2634be87 --- /dev/null +++ b/packages/core/integration-tests/test/integration/resolver-can-invalidateonenvchange/package.json @@ -0,0 +1,4 @@ +{ + "name": "test-resolver-invalidateonenevchange", + "private": true +} \ No newline at end of file diff --git a/packages/core/integration-tests/test/plugin.js b/packages/core/integration-tests/test/plugin.js index 26256535e64..67544011bf4 100644 --- a/packages/core/integration-tests/test/plugin.js +++ b/packages/core/integration-tests/test/plugin.js @@ -190,4 +190,25 @@ parcel-transformer-b`, assert.equal(await run(b), 2); }); + + it('should allow resolvers to invalidateOnEnvChange', async () => { + async function assertAsset(replacedCode) { + let b = await bundle( + path.join( + __dirname, + '/integration/resolver-invalidateonenvchange/index.js', + ), + { + shouldDisableCache: false, + inputFS: overlayFS, + logLevel: 'verbose', + env: {replacedCode}, + }, + ); + let code = await b.getBundles()[0].getEntryAssets()[0].getCode(); + assert(code.indexOf(replacedCode) !== -1); + } + assertAsset('const replaced = 1;'); + assertAsset('const replaced = 2;'); + }); }); From c18a170354713a585e3dbf0f3d72610aa9bfb374 Mon Sep 17 00:00:00 2001 From: Tony Hallett Date: Tue, 24 May 2022 09:15:55 +0100 Subject: [PATCH 5/5] correction --- .../resolver-can-invalidateonenvchange/yarn.lock | 0 packages/core/integration-tests/test/plugin.js | 7 +++---- 2 files changed, 3 insertions(+), 4 deletions(-) create mode 100644 packages/core/integration-tests/test/integration/resolver-can-invalidateonenvchange/yarn.lock diff --git a/packages/core/integration-tests/test/integration/resolver-can-invalidateonenvchange/yarn.lock b/packages/core/integration-tests/test/integration/resolver-can-invalidateonenvchange/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 67544011bf4..5b86ca5fbe9 100644 --- a/packages/core/integration-tests/test/plugin.js +++ b/packages/core/integration-tests/test/plugin.js @@ -196,19 +196,18 @@ parcel-transformer-b`, let b = await bundle( path.join( __dirname, - '/integration/resolver-invalidateonenvchange/index.js', + '/integration/resolver-can-invalidateonenvchange/index.js', ), { shouldDisableCache: false, inputFS: overlayFS, - logLevel: 'verbose', env: {replacedCode}, }, ); let code = await b.getBundles()[0].getEntryAssets()[0].getCode(); assert(code.indexOf(replacedCode) !== -1); } - assertAsset('const replaced = 1;'); - assertAsset('const replaced = 2;'); + await assertAsset('const replaced = 1;'); + await assertAsset('const replaced = 2;'); }); });