mirror of https://github.com/status-im/metro.git
Simplify Module.js
Reviewed By: rafeca Differential Revision: D7628732 fbshipit-source-id: 50f5612d94727190c372148eba1a01f245f21d73
This commit is contained in:
parent
16e843ef98
commit
01827a0fab
|
@ -291,12 +291,6 @@ class Bundler {
|
|||
}
|
||||
}
|
||||
|
||||
if (!cache && code == null) {
|
||||
throw new Error(
|
||||
'When not using experimental caches, code should always be provided',
|
||||
);
|
||||
}
|
||||
|
||||
// Second, if there was no result, compute it ourselves.
|
||||
if (!data) {
|
||||
data = await this._transformer.transform(
|
||||
|
|
|
@ -7,7 +7,7 @@ Array [
|
|||
"a",
|
||||
"b",
|
||||
],
|
||||
"id": "index",
|
||||
"id": "index.js",
|
||||
"isAsset": false,
|
||||
"isPolyfill": false,
|
||||
"path": "/root/index.js",
|
||||
|
@ -15,7 +15,7 @@ Array [
|
|||
},
|
||||
Object {
|
||||
"dependencies": Array [],
|
||||
"id": "a",
|
||||
"id": "a.js",
|
||||
"isAsset": false,
|
||||
"isPolyfill": false,
|
||||
"path": "/root/a.js",
|
||||
|
@ -23,7 +23,7 @@ Array [
|
|||
},
|
||||
Object {
|
||||
"dependencies": Array [],
|
||||
"id": "b",
|
||||
"id": "b.js",
|
||||
"isAsset": false,
|
||||
"isPolyfill": false,
|
||||
"path": "/root/b.js",
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -12,9 +12,6 @@
|
|||
|
||||
const Module = require('./Module');
|
||||
|
||||
import type {TransformedCode} from '../JSTransformer/worker';
|
||||
import type {ReadResult} from './Module';
|
||||
|
||||
class AssetModule extends Module {
|
||||
getPackage() {
|
||||
return null;
|
||||
|
@ -28,12 +25,12 @@ class AssetModule extends Module {
|
|||
return true;
|
||||
}
|
||||
|
||||
_finalizeReadResult(source: string, result: TransformedCode): ReadResult {
|
||||
_readSourceCode() {
|
||||
// We do not want to return the "source code" of assets, since it's going to
|
||||
// be binary data and can potentially be very large. This source property
|
||||
// is only used to generate the sourcemaps (since we include all the
|
||||
// modules original sources in the sourcemaps).
|
||||
return {...result, source: ''};
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,26 +10,13 @@
|
|||
|
||||
'use strict';
|
||||
|
||||
const crypto = require('crypto');
|
||||
const docblock = require('jest-docblock');
|
||||
const fs = require('fs');
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
const isAbsolutePath = require('absolute-path');
|
||||
const jsonStableStringify = require('json-stable-stringify');
|
||||
const path = require('path');
|
||||
|
||||
import type {
|
||||
TransformedCode,
|
||||
Options as WorkerOptions,
|
||||
} from '../JSTransformer/worker';
|
||||
import type {GlobalTransformCache} from '../lib/GlobalTransformCache';
|
||||
import type {
|
||||
TransformCache,
|
||||
GetTransformCacheKey,
|
||||
ReadTransformProps,
|
||||
} from '../lib/TransformCaching';
|
||||
import type {Reporter} from '../lib/reporting';
|
||||
import type DependencyGraphHelpers from './DependencyGraph/DependencyGraphHelpers';
|
||||
import type ModuleCache from './ModuleCache';
|
||||
import type {LocalPath} from './lib/toLocalPath';
|
||||
import type {MetroSourceMapSegmentTuple} from 'metro-source-map';
|
||||
|
@ -49,67 +36,23 @@ export type TransformCode = (
|
|||
transformOptions: WorkerOptions,
|
||||
) => Promise<TransformedCode>;
|
||||
|
||||
export type HasteImpl = {
|
||||
getHasteName(filePath: string): string | void,
|
||||
// This exists temporarily to enforce consistency while we deprecate
|
||||
// @providesModule.
|
||||
enforceHasteNameMatches?: (
|
||||
filePath: string,
|
||||
expectedName: string | void,
|
||||
) => void,
|
||||
};
|
||||
|
||||
export type Options = {
|
||||
globalTransformCache: ?GlobalTransformCache,
|
||||
hasteImplModulePath?: string,
|
||||
reporter: Reporter,
|
||||
resetCache: boolean,
|
||||
transformCache: TransformCache,
|
||||
};
|
||||
|
||||
export type ConstructorArgs = {
|
||||
depGraphHelpers: DependencyGraphHelpers,
|
||||
experimentalCaches: boolean,
|
||||
file: string,
|
||||
getTransformCacheKey: GetTransformCacheKey,
|
||||
localPath: LocalPath,
|
||||
moduleCache: ModuleCache,
|
||||
options: Options,
|
||||
transformCode: TransformCode,
|
||||
};
|
||||
|
||||
type DocBlock = {+[key: string]: string};
|
||||
|
||||
class Module {
|
||||
localPath: LocalPath;
|
||||
path: string;
|
||||
type: string;
|
||||
|
||||
_experimentalCaches: boolean;
|
||||
|
||||
_moduleCache: ModuleCache;
|
||||
_transformCode: TransformCode;
|
||||
_getTransformCacheKey: GetTransformCacheKey;
|
||||
_depGraphHelpers: DependencyGraphHelpers;
|
||||
_options: Options;
|
||||
|
||||
_docBlock: ?DocBlock;
|
||||
_hasteNameCache: ?{+hasteName: ?string};
|
||||
_sourceCode: ?string;
|
||||
_readPromises: Map<string, Promise<ReadResult>>;
|
||||
|
||||
_readResultsByOptionsKey: Map<string, CachedReadResult>;
|
||||
|
||||
constructor({
|
||||
depGraphHelpers,
|
||||
experimentalCaches,
|
||||
file,
|
||||
getTransformCacheKey,
|
||||
localPath,
|
||||
moduleCache,
|
||||
options,
|
||||
transformCode,
|
||||
}: ConstructorArgs) {
|
||||
constructor({file, localPath, moduleCache, transformCode}: ConstructorArgs) {
|
||||
if (!isAbsolutePath(file)) {
|
||||
throw new Error('Expected file to be absolute path but got ' + file);
|
||||
}
|
||||
|
@ -118,295 +61,59 @@ class Module {
|
|||
this.path = file;
|
||||
this.type = 'Module';
|
||||
|
||||
this._experimentalCaches = experimentalCaches;
|
||||
|
||||
this._moduleCache = moduleCache;
|
||||
this._transformCode = transformCode;
|
||||
this._getTransformCacheKey = getTransformCacheKey;
|
||||
this._depGraphHelpers = depGraphHelpers;
|
||||
this._options = options || {};
|
||||
|
||||
this._readPromises = new Map();
|
||||
this._readResultsByOptionsKey = new Map();
|
||||
}
|
||||
|
||||
isHaste(): boolean {
|
||||
return this._getHasteName() != null;
|
||||
return false;
|
||||
}
|
||||
|
||||
getName(): string {
|
||||
// TODO: T26134860 Used for debugging purposes only; disabled with the new
|
||||
// caches.
|
||||
if (this._experimentalCaches) {
|
||||
return path.basename(this.path);
|
||||
}
|
||||
|
||||
if (this.isHaste()) {
|
||||
const name = this._getHasteName();
|
||||
if (name != null) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
const p = this.getPackage();
|
||||
|
||||
if (!p) {
|
||||
// Name is local path
|
||||
return this.localPath;
|
||||
}
|
||||
|
||||
const packageName = p.getName();
|
||||
if (!packageName) {
|
||||
return this.path;
|
||||
}
|
||||
|
||||
return path
|
||||
.join(packageName, path.relative(p.root, this.path))
|
||||
.replace(/\\/g, '/');
|
||||
return this.localPath;
|
||||
}
|
||||
|
||||
getPackage() {
|
||||
return this._moduleCache.getPackageForModule(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* We don't need to invalidate the TranformCache itself because it guarantees
|
||||
* itself that if a source code changed we won't return the cached transformed
|
||||
* code.
|
||||
*/
|
||||
invalidate() {
|
||||
this._sourceCode = null;
|
||||
|
||||
// TODO: T26134860 Caches present in Module are not used with experimental
|
||||
// caches, except for the one related to source code.
|
||||
if (this._experimentalCaches) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._readPromises.clear();
|
||||
this._readResultsByOptionsKey.clear();
|
||||
this._docBlock = null;
|
||||
this._hasteNameCache = null;
|
||||
}
|
||||
|
||||
_readSourceCode(): string {
|
||||
if (this._sourceCode == null) {
|
||||
this._sourceCode = fs.readFileSync(this.path, 'utf8');
|
||||
}
|
||||
|
||||
return this._sourceCode;
|
||||
}
|
||||
|
||||
_readDocBlock(): DocBlock {
|
||||
if (this._docBlock == null) {
|
||||
this._docBlock = docblock.parse(docblock.extract(this._readSourceCode()));
|
||||
}
|
||||
return this._docBlock;
|
||||
async read(transformOptions: WorkerOptions): Promise<ReadResult> {
|
||||
const result: TransformedCode = await this._transformCode(
|
||||
this,
|
||||
null, // Source code is read on the worker
|
||||
transformOptions,
|
||||
);
|
||||
|
||||
const module = this;
|
||||
|
||||
return {
|
||||
code: result.code,
|
||||
dependencies: result.dependencies,
|
||||
map: result.map,
|
||||
get source() {
|
||||
return module._readSourceCode();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
_getHasteName(): ?string {
|
||||
if (this._hasteNameCache == null) {
|
||||
this._hasteNameCache = {hasteName: this._readHasteName()};
|
||||
}
|
||||
return this._hasteNameCache.hasteName;
|
||||
}
|
||||
|
||||
/**
|
||||
* If a custom Haste implementation is provided, then we use it to determine
|
||||
* the actual Haste name instead of "@providesModule".
|
||||
* `enforceHasteNameMatches` has been added to that it is easier to
|
||||
* transition from a system using "@providesModule" to a system using another
|
||||
* custom system, by throwing if inconsistencies are detected. For example,
|
||||
* we could verify that the file's basename (ex. "bar/foo.js") is the same as
|
||||
* the "@providesModule" name (ex. "foo").
|
||||
*/
|
||||
_readHasteName(): ?string {
|
||||
const hasteImplModulePath = this._options.hasteImplModulePath;
|
||||
if (hasteImplModulePath == null) {
|
||||
return this._readHasteNameFromDocBlock();
|
||||
}
|
||||
// eslint-disable-next-line no-useless-call
|
||||
const HasteImpl = (require.call(null, hasteImplModulePath): HasteImpl);
|
||||
if (HasteImpl.enforceHasteNameMatches != null) {
|
||||
const name = this._readHasteNameFromDocBlock();
|
||||
HasteImpl.enforceHasteNameMatches(this.path, name || undefined);
|
||||
}
|
||||
return HasteImpl.getHasteName(this.path);
|
||||
}
|
||||
|
||||
/**
|
||||
* We extract the Haste name from the `@providesModule` docbloc field. This is
|
||||
* not allowed for modules living in `node_modules`, except if they are
|
||||
* whitelisted.
|
||||
*/
|
||||
_readHasteNameFromDocBlock(): ?string {
|
||||
const moduleDocBlock = this._readDocBlock();
|
||||
const {providesModule} = moduleDocBlock;
|
||||
if (providesModule && !this._depGraphHelpers.isNodeModulesDir(this.path)) {
|
||||
return /^\S+/.exec(providesModule)[0];
|
||||
}
|
||||
readCached(transformOptions: WorkerOptions): null {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* To what we read from the cache or worker, we need to add id and source.
|
||||
*/
|
||||
_finalizeReadResult(source: string, result: TransformedCode): ReadResult {
|
||||
return {...result, source};
|
||||
}
|
||||
|
||||
async _transformCodeFor(
|
||||
cacheProps: ReadTransformProps,
|
||||
): Promise<TransformedCode> {
|
||||
const {_transformCode} = this;
|
||||
invariant(_transformCode != null, 'missing code transform funtion');
|
||||
const {sourceCode, transformOptions} = cacheProps;
|
||||
return await _transformCode(this, sourceCode, transformOptions);
|
||||
}
|
||||
|
||||
async _transformAndStoreCodeGlobally(
|
||||
cacheProps: ReadTransformProps,
|
||||
globalCache: GlobalTransformCache,
|
||||
): Promise<TransformedCode> {
|
||||
const result = await this._transformCodeFor(cacheProps);
|
||||
globalCache.store(globalCache.keyOf(cacheProps), result);
|
||||
return result;
|
||||
}
|
||||
|
||||
async _getTransformedCode(
|
||||
cacheProps: ReadTransformProps,
|
||||
): Promise<TransformedCode> {
|
||||
const globalCache = this._options.globalTransformCache;
|
||||
if (globalCache == null || !globalCache.shouldFetch(cacheProps)) {
|
||||
return await this._transformCodeFor(cacheProps);
|
||||
}
|
||||
const globalCachedResult = await globalCache.fetch(
|
||||
globalCache.keyOf(cacheProps),
|
||||
);
|
||||
if (globalCachedResult != null) {
|
||||
return globalCachedResult;
|
||||
}
|
||||
return await this._transformAndStoreCodeGlobally(cacheProps, globalCache);
|
||||
}
|
||||
|
||||
async _getAndCacheTransformedCode(
|
||||
cacheProps: ReadTransformProps,
|
||||
): Promise<TransformedCode> {
|
||||
const result = await this._getTransformedCode(cacheProps);
|
||||
this._options.transformCache.writeSync({...cacheProps, result});
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand for reading both from cache or from fresh for all call sites that
|
||||
* are asynchronous by default.
|
||||
*/
|
||||
async read(transformOptions: WorkerOptions): Promise<ReadResult> {
|
||||
// TODO: T26134860 Cache layer lives inside the transformer now; just call
|
||||
// the transform method.
|
||||
if (this._experimentalCaches) {
|
||||
const result: TransformedCode = await this._transformCode(
|
||||
this,
|
||||
null, // Source code is read on the worker
|
||||
transformOptions,
|
||||
);
|
||||
|
||||
const module = this;
|
||||
|
||||
return {
|
||||
code: result.code,
|
||||
dependencies: result.dependencies,
|
||||
map: result.map,
|
||||
get source() {
|
||||
return module._readSourceCode();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const cached = this.readCached(transformOptions);
|
||||
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
return this.readFresh(transformOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as `readFresh`, but reads from the cache instead of transforming
|
||||
* the file from source. This has the benefit of being synchronous. As a
|
||||
* result it is possible to read many cached Module in a row, synchronously.
|
||||
*/
|
||||
readCached(transformOptions: WorkerOptions): CachedReadResult {
|
||||
const key = stableObjectHash(transformOptions || {});
|
||||
let result = this._readResultsByOptionsKey.get(key);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
result = this._readFromTransformCache(transformOptions, key);
|
||||
this._readResultsByOptionsKey.set(key, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read again from the TransformCache, on disk. `readCached` should be favored
|
||||
* so it's faster in case the results are already in memory.
|
||||
*/
|
||||
_readFromTransformCache(
|
||||
transformOptions: WorkerOptions,
|
||||
transformOptionsKey: string,
|
||||
): CachedReadResult {
|
||||
const cacheProps = this._getCacheProps(
|
||||
transformOptions,
|
||||
transformOptionsKey,
|
||||
);
|
||||
const cachedResult = this._options.transformCache.readSync(cacheProps);
|
||||
|
||||
if (cachedResult == null) {
|
||||
return null;
|
||||
}
|
||||
return this._finalizeReadResult(cacheProps.sourceCode, cachedResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gathers relevant data about a module: source code, transformed code,
|
||||
* dependencies, etc. This function reads and transforms the source from
|
||||
* scratch. We don't repeat the same work as `readCached` because we assume
|
||||
* call sites have called it already.
|
||||
*/
|
||||
readFresh(transformOptions: WorkerOptions): Promise<ReadResult> {
|
||||
const key = stableObjectHash(transformOptions || {});
|
||||
const promise = this._readPromises.get(key);
|
||||
if (promise != null) {
|
||||
return promise;
|
||||
}
|
||||
const freshPromise = (async () => {
|
||||
const cacheProps = this._getCacheProps(transformOptions, key);
|
||||
const freshResult = await this._getAndCacheTransformedCode(cacheProps);
|
||||
const finalResult = this._finalizeReadResult(
|
||||
cacheProps.sourceCode,
|
||||
freshResult,
|
||||
);
|
||||
this._readResultsByOptionsKey.set(key, finalResult);
|
||||
return finalResult;
|
||||
})();
|
||||
this._readPromises.set(key, freshPromise);
|
||||
return freshPromise;
|
||||
}
|
||||
|
||||
_getCacheProps(transformOptions: WorkerOptions, transformOptionsKey: string) {
|
||||
const sourceCode = this._readSourceCode();
|
||||
const getTransformCacheKey = this._getTransformCacheKey;
|
||||
return {
|
||||
filePath: this.path,
|
||||
localPath: this.localPath,
|
||||
sourceCode,
|
||||
getTransformCacheKey,
|
||||
transformOptions,
|
||||
transformOptionsKey,
|
||||
cacheOptions: {
|
||||
resetCache: this._options.resetCache,
|
||||
reporter: this._options.reporter,
|
||||
},
|
||||
};
|
||||
return this.read(transformOptions);
|
||||
}
|
||||
|
||||
hash() {
|
||||
|
@ -422,19 +129,4 @@ class Module {
|
|||
}
|
||||
}
|
||||
|
||||
// use weak map to speed up hash creation of known objects
|
||||
const knownHashes = new WeakMap();
|
||||
function stableObjectHash(object) {
|
||||
let digest = knownHashes.get(object);
|
||||
if (!digest) {
|
||||
digest = crypto
|
||||
.createHash('md5')
|
||||
.update(jsonStableStringify(object))
|
||||
.digest('base64');
|
||||
knownHashes.set(object, digest);
|
||||
}
|
||||
|
||||
return digest;
|
||||
}
|
||||
|
||||
module.exports = Module;
|
||||
|
|
|
@ -10,363 +10,94 @@
|
|||
|
||||
'use strict';
|
||||
|
||||
jest
|
||||
.mock('fs', () => new (require('metro-memory-fs'))())
|
||||
.mock('graceful-fs')
|
||||
.mock('../ModuleCache')
|
||||
.mock('../DependencyGraph/DependencyGraphHelpers')
|
||||
.mock('../../lib/TransformCaching');
|
||||
jest.mock('fs').mock('../ModuleCache');
|
||||
|
||||
const Module = require('../Module');
|
||||
const ModuleCache = require('../ModuleCache');
|
||||
const DependencyGraphHelpers = require('../DependencyGraph/DependencyGraphHelpers');
|
||||
const TransformCaching = require('../../lib/TransformCaching');
|
||||
const fs = require('fs');
|
||||
|
||||
const packageJson = JSON.stringify({
|
||||
name: 'arbitrary',
|
||||
version: '1.0.0',
|
||||
description: "A require('foo') story",
|
||||
});
|
||||
|
||||
function mockPackageFile() {
|
||||
fs.reset();
|
||||
fs.mkdirSync('/root');
|
||||
fs.writeFileSync('/root/package.json', packageJson);
|
||||
}
|
||||
|
||||
function mockIndexFile(indexJs) {
|
||||
fs.reset();
|
||||
fs.mkdirSync('/root');
|
||||
fs.writeFileSync('/root/index.js', indexJs);
|
||||
}
|
||||
const ModuleCache = require('../ModuleCache');
|
||||
const Module = require('../Module');
|
||||
|
||||
describe('Module', () => {
|
||||
const fileName = '/root/index.js';
|
||||
let transformCode;
|
||||
let moduleCache;
|
||||
let module;
|
||||
|
||||
let cache;
|
||||
const transformCache = TransformCaching.mocked();
|
||||
|
||||
const createCache = () => ({
|
||||
get: jest
|
||||
.genMockFn()
|
||||
.mockImplementation((filepath, field, cb) => cb(filepath)),
|
||||
invalidate: jest.genMockFn(),
|
||||
end: jest.genMockFn(),
|
||||
});
|
||||
|
||||
let transformCacheKey;
|
||||
const createModule = options =>
|
||||
new Module({
|
||||
options: {transformCache},
|
||||
transformCode: (module, sourceCode, transformOptions) => {
|
||||
return Promise.resolve({code: sourceCode});
|
||||
},
|
||||
...options,
|
||||
cache,
|
||||
file: (options && options.file) || fileName,
|
||||
depGraphHelpers: new DependencyGraphHelpers(),
|
||||
localPath: (options && options.localPath) || fileName,
|
||||
moduleCache: new ModuleCache({cache}),
|
||||
getTransformCacheKey: () => transformCacheKey,
|
||||
beforeEach(() => {
|
||||
transformCode = jest.fn().mockReturnValue({
|
||||
code: 'int main(void) { return -1; }',
|
||||
dependencies: ['stdlib.h', 'conio.h'],
|
||||
map: [],
|
||||
});
|
||||
|
||||
const createJSONModule = options =>
|
||||
createModule({...options, file: '/root/package.json'});
|
||||
moduleCache = new ModuleCache();
|
||||
|
||||
beforeEach(function() {
|
||||
Object.defineProperty(process, 'platform', {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
value: 'linux',
|
||||
});
|
||||
cache = createCache();
|
||||
transformCacheKey = 'abcdef';
|
||||
transformCache.mock.reset();
|
||||
});
|
||||
|
||||
describe('Experimental caches', () => {
|
||||
it('Calls into the transformer directly when having experimental caches on', async () => {
|
||||
const transformCode = jest.fn().mockReturnValue({
|
||||
code: 'code',
|
||||
dependencies: ['dep1', 'dep2'],
|
||||
map: [],
|
||||
});
|
||||
|
||||
const module = new Module({
|
||||
cache,
|
||||
experimentalCaches: true,
|
||||
depGraphHelpers: new DependencyGraphHelpers(),
|
||||
file: fileName,
|
||||
getTransformCacheKey: () => transformCacheKey,
|
||||
localPath: fileName,
|
||||
moduleCache: new ModuleCache({cache}),
|
||||
options: {transformCache},
|
||||
transformCode,
|
||||
});
|
||||
|
||||
mockIndexFile('originalCode');
|
||||
jest.spyOn(fs, 'readFileSync');
|
||||
|
||||
// Read the first time, transform code is called.
|
||||
const res1 = await module.read({foo: 3});
|
||||
expect(res1.code).toBe('code');
|
||||
expect(res1.dependencies).toEqual(['dep1', 'dep2']);
|
||||
expect(transformCode).toHaveBeenCalledTimes(1);
|
||||
|
||||
// Read a second time, transformCode is called again!
|
||||
const res2 = await module.read({foo: 3});
|
||||
expect(res2.code).toBe('code');
|
||||
expect(res2.dependencies).toEqual(['dep1', 'dep2']);
|
||||
expect(transformCode).toHaveBeenCalledTimes(2);
|
||||
|
||||
// Code was never read, though, because experimental caches read on the
|
||||
// worker, to speed up local cache!
|
||||
expect(fs.readFileSync).not.toHaveBeenCalled();
|
||||
module = new Module({
|
||||
file: '/root/to/file.js',
|
||||
localPath: 'file.js',
|
||||
moduleCache,
|
||||
transformCode,
|
||||
});
|
||||
});
|
||||
|
||||
describe('Module ID', () => {
|
||||
const moduleId = 'arbitraryModule';
|
||||
const source = `/**
|
||||
* @providesModule ${moduleId}
|
||||
*/
|
||||
`;
|
||||
afterEach(() => {
|
||||
fs.readFileSync.mockReset();
|
||||
});
|
||||
|
||||
let module;
|
||||
beforeEach(() => {
|
||||
module = createModule();
|
||||
});
|
||||
it('Returns the correct values for many properties and methods', () => {
|
||||
expect(module.localPath).toBe('file.js');
|
||||
expect(module.path).toBe('/root/to/file.js');
|
||||
expect(module.type).toBe('Module');
|
||||
|
||||
describe('@providesModule annotations', () => {
|
||||
beforeEach(() => {
|
||||
mockIndexFile(source);
|
||||
});
|
||||
expect(module.hash()).toBeDefined();
|
||||
expect(module.getName()).toBe('file.js');
|
||||
expect(module.isHaste()).toBe(false);
|
||||
expect(module.isAsset()).toBe(false);
|
||||
expect(module.isPolyfill()).toBe(false);
|
||||
});
|
||||
|
||||
it('extracts the module name from the header', () => {
|
||||
expect(module.getName()).toEqual(moduleId);
|
||||
});
|
||||
it('reads the modules correctly', () => {
|
||||
const opts = {};
|
||||
|
||||
it('identifies the module as haste module', () => {
|
||||
expect(module.isHaste()).toBe(true);
|
||||
});
|
||||
// Caches are not in Module.js anymore.
|
||||
expect(module.readCached()).toBe(null);
|
||||
|
||||
it('does not transform the file in order to access the name', () => {
|
||||
const transformCode = jest
|
||||
.genMockFn()
|
||||
.mockReturnValue(Promise.resolve());
|
||||
// When reading fresh, we call directly into read.
|
||||
module.readFresh(opts);
|
||||
expect(transformCode.mock.calls[0][0]).toBe(module);
|
||||
expect(transformCode.mock.calls[0][1]).toBe(null);
|
||||
expect(transformCode.mock.calls[0][2]).toBe(opts);
|
||||
});
|
||||
|
||||
createModule({transformCode}).getName();
|
||||
expect(transformCode).not.toBeCalled();
|
||||
});
|
||||
it('returns the result from the transform code straight away', async () => {
|
||||
fs.readFileSync.mockReturnValue('original code');
|
||||
|
||||
it('does not transform the file in order to access the haste status', () => {
|
||||
const transformCode = jest
|
||||
.genMockFn()
|
||||
.mockReturnValue(Promise.resolve());
|
||||
createModule({transformCode}).isHaste();
|
||||
expect(transformCode).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('no annotation', () => {
|
||||
beforeEach(() => {
|
||||
mockIndexFile('arbitrary(code);');
|
||||
});
|
||||
|
||||
it('uses the file name as module name', () => {
|
||||
expect(module.getName()).toEqual(fileName);
|
||||
});
|
||||
|
||||
it('does not identify the module as haste module', () =>
|
||||
expect(module.isHaste()).toBe(false));
|
||||
|
||||
it('does not transform the file in order to access the name', () => {
|
||||
const transformCode = jest
|
||||
.genMockFn()
|
||||
.mockReturnValue(Promise.resolve());
|
||||
|
||||
createModule({transformCode}).getName();
|
||||
expect(transformCode).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('does not transform the file in order to access the haste status', () => {
|
||||
const transformCode = jest
|
||||
.genMockFn()
|
||||
.mockReturnValue(Promise.resolve());
|
||||
createModule({transformCode}).isHaste();
|
||||
expect(transformCode).not.toBeCalled();
|
||||
});
|
||||
expect(await module.read({})).toEqual({
|
||||
code: 'int main(void) { return -1; }',
|
||||
dependencies: ['stdlib.h', 'conio.h'],
|
||||
map: [],
|
||||
source: 'original code',
|
||||
});
|
||||
});
|
||||
|
||||
describe('Code', () => {
|
||||
const fileContents = 'arbitrary(code)';
|
||||
beforeEach(function() {
|
||||
mockIndexFile(fileContents);
|
||||
});
|
||||
it('checks that code is only read once until invalidated', async () => {
|
||||
fs.readFileSync.mockReturnValue('original code');
|
||||
|
||||
it('exposes file contents as `code` property on the data exposed by `read()`', () =>
|
||||
createModule()
|
||||
.read()
|
||||
.then(({code}) => expect(code).toBe(fileContents)));
|
||||
});
|
||||
// Read once. No access to "source", so no reads.
|
||||
await module.read({});
|
||||
expect(fs.readFileSync).toHaveBeenCalledTimes(0);
|
||||
|
||||
describe('Custom Code Transform', () => {
|
||||
let transformCode;
|
||||
let transformResult;
|
||||
const fileContents = 'arbitrary(code);';
|
||||
const exampleCode = `
|
||||
${'require'}('a');
|
||||
${'System.import'}('b');
|
||||
${'require'}('c');`;
|
||||
// Read again, accessing "source".
|
||||
expect((await module.read({})).source).toEqual('original code');
|
||||
expect(fs.readFileSync).toHaveBeenCalledTimes(1);
|
||||
|
||||
beforeEach(function() {
|
||||
transformResult = {code: ''};
|
||||
transformCode = jest
|
||||
.genMockFn()
|
||||
.mockImplementation((module, sourceCode, options) => {
|
||||
transformCache.writeSync({
|
||||
filePath: module.path,
|
||||
sourceCode,
|
||||
transformOptions: options,
|
||||
getTransformCacheKey: () => transformCacheKey,
|
||||
result: transformResult,
|
||||
});
|
||||
return Promise.resolve(transformResult);
|
||||
});
|
||||
mockIndexFile(fileContents);
|
||||
});
|
||||
// Read again, accessing "source" again. Still 1 because code was cached.
|
||||
expect((await module.read({})).source).toEqual('original code');
|
||||
expect(fs.readFileSync).toHaveBeenCalledTimes(1);
|
||||
|
||||
it('passes the module and file contents to the transform function when reading', () => {
|
||||
const module = createModule({transformCode});
|
||||
return module.read().then(() => {
|
||||
expect(transformCode).toBeCalledWith(module, fileContents, undefined);
|
||||
});
|
||||
});
|
||||
// Invalidate.
|
||||
module.invalidate();
|
||||
|
||||
it('passes any additional options to the transform function when reading', () => {
|
||||
const module = createModule({transformCode});
|
||||
const transformOptions = {arbitrary: Object()};
|
||||
return module
|
||||
.read(transformOptions)
|
||||
.then(() =>
|
||||
expect(transformCode.mock.calls[0][2]).toBe(transformOptions),
|
||||
);
|
||||
});
|
||||
|
||||
it('passes the module and file contents to the transform for JSON files', () => {
|
||||
mockPackageFile();
|
||||
const module = createJSONModule({transformCode});
|
||||
return module.read().then(() => {
|
||||
expect(transformCode).toBeCalledWith(module, packageJson, undefined);
|
||||
});
|
||||
});
|
||||
|
||||
it('does not extend the passed options object for JSON files', () => {
|
||||
mockPackageFile();
|
||||
const module = createJSONModule({transformCode});
|
||||
const options = {arbitrary: 'foo'};
|
||||
return module.read(options).then(() => {
|
||||
expect(transformCode).toBeCalledWith(module, packageJson, options);
|
||||
});
|
||||
});
|
||||
|
||||
it('uses dependencies that `transformCode` resolves to, instead of extracting them', async () => {
|
||||
const mockedDependencies = ['foo', 'bar'];
|
||||
transformResult = {
|
||||
code: exampleCode,
|
||||
dependencies: mockedDependencies,
|
||||
};
|
||||
const module = createModule({transformCode});
|
||||
const data = await module.read();
|
||||
|
||||
expect(data.dependencies).toEqual(mockedDependencies);
|
||||
});
|
||||
|
||||
it('forwards all additional properties of the result provided by `transformCode`', () => {
|
||||
transformResult = {
|
||||
code: exampleCode,
|
||||
arbitrary: 'arbitrary',
|
||||
dependencyOffsets: [12, 764],
|
||||
map: {version: 3},
|
||||
subObject: {foo: 'bar'},
|
||||
};
|
||||
const module = createModule({transformCode});
|
||||
|
||||
return module.read().then(result => {
|
||||
expect(result).toEqual(jasmine.objectContaining(transformResult));
|
||||
});
|
||||
});
|
||||
|
||||
it('exposes the transformed code rather than the raw file contents', async () => {
|
||||
transformResult = {code: exampleCode};
|
||||
const module = createModule({transformCode});
|
||||
const data = await module.read();
|
||||
|
||||
expect(data.code).toBe(exampleCode);
|
||||
});
|
||||
|
||||
it('exposes the raw file contents as `source` property', () => {
|
||||
const module = createModule({transformCode});
|
||||
return module.read().then(data => expect(data.source).toBe(fileContents));
|
||||
});
|
||||
|
||||
it('exposes a source map returned by the transform', async () => {
|
||||
const map = {version: 3};
|
||||
transformResult = {map, code: exampleCode};
|
||||
const module = createModule({transformCode});
|
||||
const data = await module.read();
|
||||
|
||||
expect(data.map).toBe(map);
|
||||
});
|
||||
|
||||
it('caches the transform result for the same transform options', () => {
|
||||
let module = createModule({transformCode});
|
||||
return module.read().then(() => {
|
||||
expect(transformCode).toHaveBeenCalledTimes(1);
|
||||
// We want to check transform caching rather than shallow caching of
|
||||
// Promises returned by read().
|
||||
module = createModule({transformCode});
|
||||
return module.read().then(() => {
|
||||
expect(transformCode).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('triggers a new transform for different transform options', () => {
|
||||
const module = createModule({transformCode});
|
||||
return module.read({foo: 1}).then(() => {
|
||||
expect(transformCode).toHaveBeenCalledTimes(1);
|
||||
return module.read({foo: 2}).then(() => {
|
||||
expect(transformCode).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('triggers a new transform for different source code', () => {
|
||||
let module = createModule({transformCode});
|
||||
return module.read().then(() => {
|
||||
expect(transformCode).toHaveBeenCalledTimes(1);
|
||||
cache = createCache();
|
||||
mockIndexFile('test');
|
||||
module = createModule({transformCode});
|
||||
return module.read().then(() => {
|
||||
expect(transformCode).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('triggers a new transform for different transform cache key', () => {
|
||||
let module = createModule({transformCode});
|
||||
return module.read().then(() => {
|
||||
expect(transformCode).toHaveBeenCalledTimes(1);
|
||||
transformCacheKey = 'other';
|
||||
module = createModule({transformCode});
|
||||
return module.read().then(() => {
|
||||
expect(transformCode).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
// Read again, this time it will read it.
|
||||
expect((await module.read({})).source).toEqual('original code');
|
||||
expect(fs.readFileSync).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue