Skip to content

Commit

Permalink
fix(babel): modules being erroneously evaluated as empty objects (fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
Anber committed Feb 16, 2023
1 parent 13f0b41 commit f9df4ed
Show file tree
Hide file tree
Showing 9 changed files with 212 additions and 97 deletions.
7 changes: 7 additions & 0 deletions .changeset/sour-taxis-hear.md
@@ -0,0 +1,7 @@
---
'@linaria/babel-preset': patch
'@linaria/testkit': patch
'@linaria/utils': patch
---

Address the problem in which a module may be erroneously evaluated as an empty object (fixes #1209)
2 changes: 1 addition & 1 deletion packages/babel/src/index.ts
Expand Up @@ -17,7 +17,7 @@ export { default as preeval } from './plugins/preeval';
export * from './utils/collectTemplateDependencies';
export { default as collectTemplateDependencies } from './utils/collectTemplateDependencies';
export { default as withLinariaMetadata } from './utils/withLinariaMetadata';
export { default as Module } from './module';
export { default as Module, DefaultModuleImplementation } from './module';
export { default as transform } from './transform';
export * from './types';
export { default as loadLinariaOptions } from './transform-stages/helpers/loadLinariaOptions';
Expand Down
96 changes: 29 additions & 67 deletions packages/babel/src/module.ts
Expand Up @@ -28,6 +28,18 @@ import { TransformCacheCollection } from './cache';
import * as process from './process';
import type { ITransformFileResult } from './types';

type HiddenModuleMembers = {
_extensions: { [key: string]: () => void };
_nodeModulePaths(filename: string): string[];
_resolveFilename: (
id: string,
options: { id: string; filename: string; paths: string[] }
) => string;
};

export const DefaultModuleImplementation = NativeModule as typeof NativeModule &
HiddenModuleMembers;

// Supported node builtins based on the modules polyfilled by webpack
// `true` means module is polyfilled, `false` means module is empty
const builtins = {
Expand Down Expand Up @@ -87,23 +99,10 @@ const hasKey = <TKey extends string | symbol>(
key in obj;

class Module {
static invalidate: () => void;

static invalidateEvalCache: () => void;

static _resolveFilename: (
id: string,
options: { id: string; filename: string; paths: string[] }
) => string;

static _nodeModulePaths: (filename: string) => string[];

#isEvaluated = false;

#exports: Record<string, unknown> | unknown;

// #exportsProxy: Record<string, unknown>;

#lazyValues: Map<string | symbol, () => unknown>;

readonly idx: number;
Expand All @@ -116,7 +115,7 @@ class Module {

imports: Map<string, string[]> | null;

paths: string[];
// paths: string[];

extensions: string[];

Expand Down Expand Up @@ -146,14 +145,14 @@ class Module {
options: StrictOptions,
cache = new TransformCacheCollection(),
private debuggerDepth = 0,
private parentModule?: Module
private parentModule?: Module,
private moduleImpl: HiddenModuleMembers = DefaultModuleImplementation
) {
this.idx = getFileIdx(filename);
this.id = filename;
this.filename = filename;
this.options = options;
this.imports = null;
this.paths = [];
this.dependencies = null;
this.transform = null;
this.debug = createCustomDebug('module', this.idx);
Expand All @@ -162,27 +161,6 @@ class Module {
this.#codeCache = cache.codeCache;
this.#evalCache = cache.evalCache;

Object.defineProperties(this, {
id: {
value: filename,
writable: false,
},
filename: {
value: filename,
writable: false,
},
paths: {
value: Object.freeze(
(
NativeModule as unknown as {
_nodeModulePaths(filename: string): string[];
}
)._nodeModulePaths(path.dirname(filename))
),
writable: false,
},
});

this.#lazyValues = new Map();

const exports: Record<string | symbol, unknown> = {};
Expand Down Expand Up @@ -305,11 +283,7 @@ class Module {
return this.#resolveCache.get(resolveCacheKey)!;
}

const extensions = (
NativeModule as unknown as {
_extensions: { [key: string]: () => void };
}
)._extensions;
const extensions = this.moduleImpl._extensions;
const added: string[] = [];

try {
Expand All @@ -326,7 +300,13 @@ class Module {
added.push(ext);
});

return Module._resolveFilename(id, this);
const { filename } = this;

return this.moduleImpl._resolveFilename(id, {
id: filename,
filename,
paths: this.moduleImpl._nodeModulePaths(path.dirname(filename)),
});
} finally {
// Cleanup the extensions we added to restore previous behaviour
added.forEach((ext) => delete extensions[ext]);
Expand Down Expand Up @@ -399,10 +379,10 @@ class Module {
if (extension === '.json' || this.extensions.includes(extension)) {
let code: string | undefined;
// Requested file can be already prepared for evaluation on the stage 1
if (this.#codeCache.has(filename)) {
if (onlyList && this.#codeCache.has(filename)) {
const cached = this.#codeCache.get(filename);
const only = onlyList
?.split(',')
.split(',')
.filter((token) => !m.#lazyValues.has(token));
const cachedOnly = new Set(cached?.only ?? []);
const isMatched =
Expand All @@ -427,7 +407,10 @@ class Module {
} else {
// If code wasn't extracted from cache, read it from the file system
// TODO: transpile the file
m.debug('code-cache', '❌');
m.debug(
'code-cache',
'❌ file has not been processed during prepare stage'
);
code = fs.readFileSync(filename, 'utf-8');
}

Expand Down Expand Up @@ -519,25 +502,4 @@ class Module {
}
}

Module.invalidate = () => {};

Module.invalidateEvalCache = () => {};

// Alias to resolve the module using node's resolve algorithm
// This static property can be overriden by the webpack loader
// This allows us to use webpack's module resolution algorithm
Module._resolveFilename = (id, options) =>
(
NativeModule as unknown as {
_resolveFilename: typeof Module._resolveFilename;
}
)._resolveFilename(id, options);

Module._nodeModulePaths = (filename: string) =>
(
NativeModule as unknown as {
_nodeModulePaths: (filename: string) => string[];
}
)._nodeModulePaths(filename);

export default Module;
22 changes: 19 additions & 3 deletions packages/babel/src/transform-stages/1-prepare-for-eval.ts
Expand Up @@ -285,11 +285,27 @@ export function prepareForEvalSync(
for (const [importedFile, importsOnly] of imports ?? []) {
try {
const resolved = resolve(importedFile, name, resolveStack);
log('stage-1:sync-resolve', `✅ ${importedFile} -> ${resolved}`);
log(
'stage-1:sync-resolve',
`✅ ${importedFile} -> ${resolved} (only: %o)`,
importsOnly
);

const resolveCacheKey = `${name} -> ${importedFile}`;
const resolveCached = cache.resolveCache.get(resolveCacheKey);
const importsOnlySet = new Set(importsOnly);
if (resolveCached) {
const [, cachedOnly] = resolveCached.split('\0');
cachedOnly?.split(',').forEach((token) => {
importsOnlySet.add(token);
});
}

cache.resolveCache.set(
`${name} -> ${importedFile}`,
`${resolved}\0${importsOnly.join(',')}`
resolveCacheKey,
`${resolved}\0${[...importsOnlySet].join(',')}`
);

const fileContent = readFileSync(resolved, 'utf8');
queue.enqueue([
{
Expand Down
4 changes: 0 additions & 4 deletions packages/testkit/src/babel.test.ts
Expand Up @@ -8,7 +8,6 @@ import stripAnsi from 'strip-ansi';

import type { PluginOptions, Stage } from '@linaria/babel-preset';
import {
Module,
transform as linariaTransform,
loadLinariaOptions,
} from '@linaria/babel-preset';
Expand Down Expand Up @@ -139,9 +138,6 @@ async function transformFile(filename: string, opts: Options) {

describe('strategy shaker', () => {
const evaluator = require('@linaria/shaker').default;
beforeEach(() => {
Module.invalidateEvalCache();
});

it('transpiles styled template literal with object', async () => {
const { code, metadata } = await transform(
Expand Down
22 changes: 22 additions & 0 deletions packages/testkit/src/collectExportsAndImports.test.ts
Expand Up @@ -734,6 +734,28 @@ describe.each(compilers)('collectExportsAndImports (%s)', (name, compiler) => {
}
});

it('__exportStar', () => {
const { exports, imports, reexports } = run`
const tslib_1 = require('tslib');
tslib_1.__exportStar(require('./moduleA1'), exports);
`;

expect(reexports.map(withoutLocal)).toMatchObject([
{
imported: '*',
exported: '*',
source: './moduleA1',
},
]);
expect(exports).toHaveLength(0);
expect(imports).toMatchObject([
{
source: 'tslib',
imported: '__exportStar',
},
]);
});

it('mixed exports', () => {
const { exports, imports, reexports } = run`
export { syncResolve } from './asyncResolveFallback';
Expand Down

0 comments on commit f9df4ed

Please sign in to comment.