From 194c9a5d1225dd232e4a89d6c78141af87c8229b Mon Sep 17 00:00:00 2001 From: 10Derozan Date: Tue, 17 Oct 2023 14:19:50 +0800 Subject: [PATCH] fix(module-tools): load empty object instead throw error when js resolve result is false (#4796) --- .changeset/big-chefs-wait.md | 6 +++ .../src/builder/esbuild/adapter.ts | 20 +++++++-- .../module-tools/src/builder/esbuild/index.ts | 8 ++-- .../src/builder/esbuild/resolve.ts | 43 +++++++++++++------ .../module-tools/src/types/esbuild.ts | 2 +- pnpm-lock.yaml | 14 +++++- .../build/resolve/data-url/modern.config.ts | 6 ++- .../build/resolve/false/false.test.ts | 14 ++++++ .../fixtures/build/resolve/false/index.ts | 6 +++ .../build/resolve/false/modern.config.ts | 7 +++ .../fixtures/build/resolve/false/package.json | 7 +++ .../resolve/node-protocol/modern.config.ts | 6 ++- 12 files changed, 114 insertions(+), 25 deletions(-) create mode 100644 .changeset/big-chefs-wait.md create mode 100644 tests/integration/module/fixtures/build/resolve/false/false.test.ts create mode 100644 tests/integration/module/fixtures/build/resolve/false/index.ts create mode 100644 tests/integration/module/fixtures/build/resolve/false/modern.config.ts create mode 100644 tests/integration/module/fixtures/build/resolve/false/package.json diff --git a/.changeset/big-chefs-wait.md b/.changeset/big-chefs-wait.md new file mode 100644 index 000000000000..090bac1c3ae0 --- /dev/null +++ b/.changeset/big-chefs-wait.md @@ -0,0 +1,6 @@ +--- +'@modern-js/module-tools': patch +--- + +fix(module-tools): load empty object instead throw error when js resolve result is false +fix(module-tools): 当 js resolve 结果为 false 时,加载空对象替代抛出错误 diff --git a/packages/solutions/module-tools/src/builder/esbuild/adapter.ts b/packages/solutions/module-tools/src/builder/esbuild/adapter.ts index 451f0d420019..b55a65692a26 100644 --- a/packages/solutions/module-tools/src/builder/esbuild/adapter.ts +++ b/packages/solutions/module-tools/src/builder/esbuild/adapter.ts @@ -186,11 +186,19 @@ export const adapterPlugin = (compiler: ICompiler): Plugin => { const isExternal = getIsExternal(originalFilePath); const dir = args.resolveDir ?? (args.importer ? dirname(args.importer) : root); + const resultPath = isExternal + ? args.path + : getResultPath(originalFilePath, dir, args.kind); + if (resultPath === false) { + debugResolve('empty resolve:', args); + return { + path: '/empty-stub', + sideEffects: false, + }; + } const sideEffects = await getSideEffects(originalFilePath, isExternal); const result = { - path: isExternal - ? args.path - : getResultPath(originalFilePath, dir, args.kind), + path: resultPath, external: isExternal, namespace: isExternal ? undefined : 'file', sideEffects, @@ -218,6 +226,12 @@ export const adapterPlugin = (compiler: ICompiler): Plugin => { return; } + if (args.path === '/empty-stub') { + return { + contents: 'module.exports = {}', + }; + } + compiler.addWatchFile(args.path); let result = await compiler.hooks.load.promise(args); if (!result) { diff --git a/packages/solutions/module-tools/src/builder/esbuild/index.ts b/packages/solutions/module-tools/src/builder/esbuild/index.ts index 0a86e830aa6e..fc696b9f9dbd 100644 --- a/packages/solutions/module-tools/src/builder/esbuild/index.ts +++ b/packages/solutions/module-tools/src/builder/esbuild/index.ts @@ -29,7 +29,7 @@ import { adapterPlugin } from './adapter'; import { TransformContext } from './transform'; import { SourcemapContext } from './sourcemap'; import { createRenderChunkHook, createTransformHook } from './hook'; -import { createResolver } from './resolve'; +import { createJsResolver, createCssResolver } from './resolve'; import { initWatcher } from './watch'; export class EsbuildCompiler implements ICompiler { @@ -57,7 +57,7 @@ export class EsbuildCompiler implements ICompiler { css_resolve: (id: string, dir: string) => string; - node_resolve: (id: string, dir: string, kind: ImportKind) => string; + node_resolve: (id: string, dir: string, kind: ImportKind) => string | false; watcher?: FSWatcher; @@ -89,13 +89,13 @@ export class EsbuildCompiler implements ICompiler { tsconfig: config.tsconfig, mainFields: config.resolve.mainFields, }; - this.css_resolve = createResolver({ + this.css_resolve = createCssResolver({ ...resolveOptions, resolveType: 'css', extensions: cssExtensions, preferRelative: true, }); - this.node_resolve = createResolver({ + this.node_resolve = createJsResolver({ ...resolveOptions, resolveType: 'js', extensions: config.resolve.jsExtensions, diff --git a/packages/solutions/module-tools/src/builder/esbuild/resolve.ts b/packages/solutions/module-tools/src/builder/esbuild/resolve.ts index 2691f5797504..8975a0209494 100644 --- a/packages/solutions/module-tools/src/builder/esbuild/resolve.ts +++ b/packages/solutions/module-tools/src/builder/esbuild/resolve.ts @@ -56,29 +56,46 @@ function createEnhancedResolve(options: ResolverOptions): { }; } -export const createResolver = (options: ResolverOptions) => { +export const createJsResolver = (options: ResolverOptions) => { const resolveCache = new Map(); const { resolveSync, esmResolveSync } = createEnhancedResolve(options); const resolver = (id: string, dir: string, kind?: ImportKind) => { const cacheKey = id + dir + (kind || ''); const cacheResult = resolveCache.get(cacheKey); - if (cacheResult) { return cacheResult; } + let result: string | false; - if (options.resolveType === 'js') { - if (kind === 'import-statement' || kind === 'dynamic-import') { - result = esmResolveSync(dir, id); - } else { - result = resolveSync(dir, id); - } + if (kind === 'import-statement' || kind === 'dynamic-import') { + result = esmResolveSync(dir, id); } else { - try { - result = resolveSync(dir, id); - } catch (err) { - result = resolveSync(dir, id.replace(/^~/, '')); - } + result = resolveSync(dir, id); + } + + if (result) { + resolveCache.set(cacheKey, result); + } + return result; + }; + return resolver; +}; + +export const createCssResolver = (options: ResolverOptions) => { + const resolveCache = new Map(); + const { resolveSync } = createEnhancedResolve(options); + const resolver = (id: string, dir: string, kind?: ImportKind) => { + const cacheKey = id + dir + (kind || ''); + const cacheResult = resolveCache.get(cacheKey); + if (cacheResult) { + return cacheResult; + } + + let result: string | false; + try { + result = resolveSync(dir, id); + } catch (err) { + result = resolveSync(dir, id.replace(/^~/, '')); } if (!result) { throw new Error(`can not resolve ${id} from ${dir}`); diff --git a/packages/solutions/module-tools/src/types/esbuild.ts b/packages/solutions/module-tools/src/types/esbuild.ts index 5f7b618c7587..aa999ddb076e 100644 --- a/packages/solutions/module-tools/src/types/esbuild.ts +++ b/packages/solutions/module-tools/src/types/esbuild.ts @@ -51,7 +51,7 @@ export type Context = { export interface ICompiler { reBuild: (type: 'link' | 'change', config: BaseBuildConfig) => Promise; css_resolve: (id: string, dir: string) => string; - node_resolve: (id: string, dir: string, kind: ImportKind) => string; + node_resolve: (id: string, dir: string, kind: ImportKind) => string | false; init: () => Promise; watcher?: FSWatcher; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index da1dc2367b51..9024d099329d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7070,6 +7070,12 @@ importers: tests/integration/module/fixtures/build/resolve/data-url: {} + tests/integration/module/fixtures/build/resolve/false: + devDependencies: + object-inspect: + specifier: 1.13.0 + version: 1.13.0 + tests/integration/module/fixtures/build/resolve/node-protocol: {} tests/integration/module/fixtures/build/resolve/with-condition-exports: {} @@ -21615,7 +21621,7 @@ packages: is-string: 1.0.7 is-typed-array: 1.1.10 is-weakref: 1.0.2 - object-inspect: 1.12.3 + object-inspect: 1.13.0 object-keys: 1.1.1 object.assign: 4.1.4 regexp.prototype.flags: 1.4.3 @@ -28018,6 +28024,10 @@ packages: /object-inspect@1.12.3: resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==} + dev: true + + /object-inspect@1.13.0: + resolution: {integrity: sha512-HQ4J+ic8hKrgIt3mqk6cVOVrW2ozL4KdvHlqpBv9vDYWx9ysAgENAdvy4FoGF+KFdhR7nQTNm5J0ctAeOwn+3g==} /object-is@1.1.5: resolution: {integrity: sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==} @@ -32369,7 +32379,7 @@ packages: dependencies: call-bind: 1.0.2 get-intrinsic: 1.2.0 - object-inspect: 1.12.3 + object-inspect: 1.13.0 /siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} diff --git a/tests/integration/module/fixtures/build/resolve/data-url/modern.config.ts b/tests/integration/module/fixtures/build/resolve/data-url/modern.config.ts index 1469f5c58c19..a6d72c126bf1 100644 --- a/tests/integration/module/fixtures/build/resolve/data-url/modern.config.ts +++ b/tests/integration/module/fixtures/build/resolve/data-url/modern.config.ts @@ -1,3 +1,7 @@ import { defineConfig } from '@modern-js/module-tools/defineConfig'; -export default defineConfig({}); +export default defineConfig({ + buildConfig: { + input: ['./index.ts'], + }, +}); diff --git a/tests/integration/module/fixtures/build/resolve/false/false.test.ts b/tests/integration/module/fixtures/build/resolve/false/false.test.ts new file mode 100644 index 000000000000..105e87f314fb --- /dev/null +++ b/tests/integration/module/fixtures/build/resolve/false/false.test.ts @@ -0,0 +1,14 @@ +import { runCli, initBeforeTest } from '../../../utils'; + +initBeforeTest(); + +describe('resolve', () => { + const fixtureDir = __dirname; + it('false', async () => { + const { success } = await runCli({ + argv: ['build'], + appDirectory: fixtureDir, + }); + expect(success).toBeTruthy(); + }); +}); diff --git a/tests/integration/module/fixtures/build/resolve/false/index.ts b/tests/integration/module/fixtures/build/resolve/false/index.ts new file mode 100644 index 000000000000..ee28b523079e --- /dev/null +++ b/tests/integration/module/fixtures/build/resolve/false/index.ts @@ -0,0 +1,6 @@ +// In some directory structure, we can not resolve './util.inspect' from 'index.js' in 'object-inspect', +// For example, in bytedance internal monorepo solution, but here we can resolve it. + +import xxx from 'object-inspect'; + +console.log('xxx:', xxx); diff --git a/tests/integration/module/fixtures/build/resolve/false/modern.config.ts b/tests/integration/module/fixtures/build/resolve/false/modern.config.ts new file mode 100644 index 000000000000..a6d72c126bf1 --- /dev/null +++ b/tests/integration/module/fixtures/build/resolve/false/modern.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from '@modern-js/module-tools/defineConfig'; + +export default defineConfig({ + buildConfig: { + input: ['./index.ts'], + }, +}); diff --git a/tests/integration/module/fixtures/build/resolve/false/package.json b/tests/integration/module/fixtures/build/resolve/false/package.json new file mode 100644 index 000000000000..4f5d605bfb27 --- /dev/null +++ b/tests/integration/module/fixtures/build/resolve/false/package.json @@ -0,0 +1,7 @@ +{ + "name": "resolve-false", + "version": "1.0.0", + "devDependencies": { + "object-inspect": "1.13.0" + } +} diff --git a/tests/integration/module/fixtures/build/resolve/node-protocol/modern.config.ts b/tests/integration/module/fixtures/build/resolve/node-protocol/modern.config.ts index 1469f5c58c19..a6d72c126bf1 100644 --- a/tests/integration/module/fixtures/build/resolve/node-protocol/modern.config.ts +++ b/tests/integration/module/fixtures/build/resolve/node-protocol/modern.config.ts @@ -1,3 +1,7 @@ import { defineConfig } from '@modern-js/module-tools/defineConfig'; -export default defineConfig({}); +export default defineConfig({ + buildConfig: { + input: ['./index.ts'], + }, +});