diff --git a/packages/core/core/src/Asset.js b/packages/core/core/src/Asset.js new file mode 100644 index 00000000000..98aef2cbaca --- /dev/null +++ b/packages/core/core/src/Asset.js @@ -0,0 +1,150 @@ +// @flow +import type { + Asset as IAsset, + TransformerResult, + DependencyOptions, + Dependency, + FilePath, + File, + Environment, + JSONObject, + AST, + AssetOutput +} from '@parcel/types'; +import md5 from '@parcel/utils/md5'; +import config from '@parcel/utils/config'; +import createDependency from './createDependency'; + +type AssetOptions = { + id?: string, + hash?: string, + filePath: FilePath, + type: string, + code?: string, + ast?: ?AST, + dependencies?: Array, + connectedFiles?: Array, + output?: AssetOutput, + env: Environment, + meta?: JSONObject +}; + +export default class Asset implements IAsset { + id: string; + hash: string; + filePath: FilePath; + type: string; + code: string; + ast: ?AST; + dependencies: Array; + connectedFiles: Array; + output: AssetOutput; + env: Environment; + meta: JSONObject; + + constructor(options: AssetOptions) { + this.id = + options.id || + md5(options.filePath + options.type + JSON.stringify(options.env)); + this.hash = options.hash || ''; + this.filePath = options.filePath; + this.type = options.type; + this.code = options.code || (options.output ? options.output.code : ''); + this.ast = options.ast || null; + this.dependencies = options.dependencies + ? options.dependencies.slice() + : []; + this.connectedFiles = options.connectedFiles + ? options.connectedFiles.slice() + : []; + this.output = options.output || {code: this.code}; + this.env = options.env; + this.meta = options.meta || {}; + } + + toJSON(): AssetOptions { + // Exclude `code` and `ast` from cache + return { + id: this.id, + hash: this.hash, + filePath: this.filePath, + type: this.type, + dependencies: this.dependencies, + connectedFiles: this.connectedFiles, + output: this.output, + env: this.env, + meta: this.meta + }; + } + + addDependency(opts: DependencyOptions) { + let dep = createDependency( + { + ...opts, + env: mergeEnvironment(this.env, opts.env) + }, + this.filePath + ); + + this.dependencies.push(dep); + return dep.id; + } + + async addConnectedFile(file: File) { + if (!file.hash) { + file.hash = await md5.file(file.filePath); + } + + this.connectedFiles.push(file); + } + + createChildAsset(result: TransformerResult) { + let code = result.code || (result.output && result.output.code) || ''; + let opts: AssetOptions = { + hash: this.hash || md5(code), + filePath: this.filePath, + type: result.type, + code, + ast: result.ast, + env: mergeEnvironment(this.env, result.env), + dependencies: this.dependencies, + connectedFiles: this.connectedFiles, + meta: Object.assign({}, this.meta, result.meta) + }; + + let asset = new Asset(opts); + + if (result.dependencies) { + for (let dep of result.dependencies) { + asset.addDependency(dep); + } + } + + if (result.connectedFiles) { + for (let file of result.connectedFiles) { + asset.addConnectedFile(file); + } + } + + return asset; + } + + async getOutput() { + return this.output; + } + + async getConfig(filePaths: Array) { + return config.load(this.filePath, filePaths); + } + + async getPackage() { + return { + name: 'foo', + version: '1.2.3' + }; + } +} + +function mergeEnvironment(a: Environment, b: ?Environment): Environment { + return Object.assign({}, a, b); +} diff --git a/packages/core/core/src/AssetGraph.js b/packages/core/core/src/AssetGraph.js index b116be40e48..5441f3146b3 100644 --- a/packages/core/core/src/AssetGraph.js +++ b/packages/core/core/src/AssetGraph.js @@ -13,6 +13,7 @@ import type { } from '@parcel/types'; import path from 'path'; import md5 from '@parcel/utils/md5'; +import createDependency from './createDependency'; export const nodeFromRootDir = (rootDir: string) => ({ id: rootDir, @@ -21,9 +22,7 @@ export const nodeFromRootDir = (rootDir: string) => ({ }); export const nodeFromDep = (dep: Dependency) => ({ - id: md5( - `${dep.sourcePath}:${dep.moduleSpecifier}:${JSON.stringify(dep.env)}` - ), + id: dep.id, type: 'dependency', value: dep }); @@ -104,11 +103,15 @@ export default class AssetGraph extends Graph { let depNodes = []; for (let entry of entries) { for (let target of targets) { - let node = nodeFromDep({ - sourcePath: path.resolve(rootDir, 'index'), - moduleSpecifier: entry, - env: target.env - }); + let node = nodeFromDep( + createDependency( + { + moduleSpecifier: entry, + env: target.env + }, + path.resolve(rootDir, 'index') + ) + ); depNodes.push(node); } @@ -182,9 +185,7 @@ export default class AssetGraph extends Graph { let removedFiles = getFilesFromGraph(removed); for (let assetNode of assetNodes) { - // TODO: dep should already have sourcePath let depNodes = assetNode.value.dependencies.map(dep => { - dep.sourcePath = req.filePath; return nodeFromDep(dep); }); let {removed, added} = this.replaceNodesConnectedTo(assetNode, depNodes); diff --git a/packages/core/core/src/Graph.js b/packages/core/core/src/Graph.js index afb06226e9a..adbc51a2e9c 100644 --- a/packages/core/core/src/Graph.js +++ b/packages/core/core/src/Graph.js @@ -141,9 +141,8 @@ export default class Graph { edgesToRemove = edgesToRemove.filter(edge => edge.to !== toNode.id); - let edge = edgesBefore.find(edge => edge.to === toNode.id); - if (!edge) { - edge = {from: fromNode.id, to: toNode.id}; + let edge = {from: fromNode.id, to: toNode.id}; + if (!this.hasEdge(edge)) { this.addEdge(edge); added.addEdge(edge); } diff --git a/packages/core/core/src/TransformerRunner.js b/packages/core/core/src/TransformerRunner.js index 98512aa1f1f..8acb2f13ecf 100644 --- a/packages/core/core/src/TransformerRunner.js +++ b/packages/core/core/src/TransformerRunner.js @@ -1,18 +1,14 @@ // @flow import type { - Asset, + Asset as IAsset, AssetOutput, CacheEntry, - Dependency, - Environment, File, - JSONObject, Transformer, TransformerRequest, - TransformerInput, - TransformerResult, CLIOptions } from '@parcel/types'; +import Asset from './Asset'; import path from 'path'; import clone from 'clone'; import md5 from '@parcel/utils/md5'; @@ -26,15 +22,7 @@ type Opts = { cache?: Cache }; -type GenerateFunc = ?(input: TransformerInput) => Promise; -type TransformContext = { - type: string, - hash?: string, - dependencies: Array, - connectedFiles: Array, - generate?: GenerateFunc, - meta?: JSONObject -}; +type GenerateFunc = ?(input: Asset) => Promise; class TransformerRunner { cliOpts: CLIOptions; @@ -61,26 +49,21 @@ class TransformerRunner { return cacheEntry; } - let input: TransformerInput = { + let input = new Asset({ filePath: req.filePath, + type: path.extname(req.filePath).slice(1), ast: null, code, env: req.env - }; - - let context = { - type: path.extname(req.filePath).slice(1), - dependencies: [], - connectedFiles: [] - }; + }); let pipeline = await this.config.getTransformers(req.filePath); let {assets, initialAssets, connectedFiles} = await this.runPipeline( input, pipeline, - cacheEntry, - context + cacheEntry ); + cacheEntry = { filePath: req.filePath, env: req.env, @@ -95,10 +78,10 @@ class TransformerRunner { } async runPipeline( - input: TransformerInput, + input: Asset, pipeline: Array, cacheEntry: ?CacheEntry, - context: TransformContext + previousGenerate: ?GenerateFunc ) { // Run the first transformer in the pipeline. let { @@ -106,25 +89,17 @@ class TransformerRunner { connectedFiles, generate, postProcess - } = await this.runTransform(input, pipeline[0], context.generate); - - context.generate = generate; + } = await this.runTransform(input, pipeline[0], previousGenerate); - let assets: Array = []; + let assets: Array = []; for (let result of results) { - let asset; - - // If this is the first transformer, create a hash for the asset. - if (!context.hash) { - asset = await transformerResultToAsset(input, result, context); - context.hash = asset.hash; - } + let asset = input.createChildAsset(result); // Check if any of the cached assets match the result. if (cacheEntry) { let cachedAssets = ( cacheEntry.initialAssets || cacheEntry.assets - ).filter(child => child.hash === context.hash); + ).filter(child => child.hash === asset.hash); if ( cachedAssets.length > 0 && @@ -136,46 +111,47 @@ class TransformerRunner { } // If the generated asset has the same type as the input... - if (result.type === context.type) { + // TODO: this is incorrect since multiple file types could map to the same pipeline. need to compare the pipelines. + if (result.type === input.type) { // If we have reached the last transform in the pipeline, then we are done. if (pipeline.length === 1) { - assets.push( - asset || (await transformerResultToAsset(input, result, context)) - ); + assets.push(await finalize(asset, generate)); } else { // Recursively run the remaining transforms in the pipeline. - let nextInput = transformerResultToInput(input, result); - let cacheEntry = await this.runPipeline( - nextInput, + let nextPipelineResult = await this.runPipeline( + asset, pipeline.slice(1), null, - getNextContext(context, result) + generate ); - assets = assets.concat(cacheEntry.assets); - connectedFiles = connectedFiles.concat(cacheEntry.connectedFiles); + assets = assets.concat(nextPipelineResult.assets); + connectedFiles = connectedFiles.concat( + nextPipelineResult.connectedFiles + ); } } else { // Jump to a different pipeline for the generated asset. - let nextInput = transformerResultToInput(input, result); let nextFilePath = input.filePath.slice(0, -path.extname(input.filePath).length) + '.' + result.type; - let cacheEntry = await this.runPipeline( - nextInput, + let nextPipelineResult = await this.runPipeline( + asset, await this.config.getTransformers(nextFilePath), null, - getNextContext(context, result) + generate ); - assets = assets.concat(cacheEntry.assets); - connectedFiles = connectedFiles.concat(cacheEntry.connectedFiles); + assets = assets.concat(nextPipelineResult.assets); + connectedFiles = connectedFiles.concat( + nextPipelineResult.connectedFiles + ); } } // If the transformer has a postProcess function, execute that with the result of the pipeline. - let finalAssets = await postProcess(clone(assets), context); + let finalAssets = await postProcess(clone(assets)); return { assets: finalAssets || assets, @@ -185,7 +161,7 @@ class TransformerRunner { } async runTransform( - input: TransformerInput, + input: Asset, transformer: Transformer, previousGenerate: GenerateFunc ) { @@ -193,7 +169,7 @@ class TransformerRunner { let config = null; let connectedFiles: Array = []; if (transformer.getConfig) { - let result = await transformer.getConfig(input.filePath, this.cliOpts); + let result = await transformer.getConfig(input, this.cliOpts); if (result) { config = result.config; connectedFiles = result.files; @@ -222,7 +198,7 @@ class TransformerRunner { let results = await transformer.transform(input, config, this.cliOpts); // Create a generate function that can be called later to lazily generate - let generate = async (input: TransformerInput): Promise => { + let generate = async (input: Asset): Promise => { if (transformer.generate) { return await transformer.generate(input, config, this.cliOpts); } @@ -234,8 +210,7 @@ class TransformerRunner { // Create a postProcess function that can be called later let postProcess = async ( - assets: Array, - context: TransformContext + assets: Array ): Promise | null> => { if (transformer.postProcess) { let results = await transformer.postProcess( @@ -245,9 +220,7 @@ class TransformerRunner { ); return Promise.all( - results.map(result => - transformerResultToAsset(input, result, context) - ) + results.map(result => input.createChildAsset(result)) ); } @@ -258,92 +231,15 @@ class TransformerRunner { } } -async function getOutput( - input: TransformerInput, - result: TransformerResult, - context: TransformContext -): Promise { - let output: AssetOutput = result.output || {code: result.code || ''}; - if (result.code) { - output = clone(output); - output.code = result.code || ''; - } - - if (result.ast && context.generate) { - output = await context.generate(transformerResultToInput(input, result)); +async function finalize(asset: Asset, generate: GenerateFunc): Promise { + if (asset.ast && generate) { + asset.output = await generate(asset); } - return output; -} - -async function transformerResultToAsset( - input: TransformerInput, - result: TransformerResult, - context: TransformContext -): Promise { - let output = await getOutput(input, result, context); - let env = mergeEnvironment(input.env, result.env); - let dependencies = (result.dependencies || []).map(dep => - toDependency(input, dep) - ); - - let connectedFiles = context.connectedFiles.concat( - result.connectedFiles || [] - ); - await Promise.all( - connectedFiles.map(async file => { - if (!file.hash) { - file.hash = await md5.file(file.filePath); - } - }) - ); - - return { - id: md5(input.filePath + result.type + JSON.stringify(env)), - hash: context.hash || md5(output.code), - filePath: input.filePath, - type: result.type, - dependencies: context.dependencies.concat(dependencies), - connectedFiles, - output, - env, - meta: Object.assign({}, context.meta, result.meta) - }; -} - -function toDependency(input: TransformerInput, dep: Dependency): Dependency { - dep.env = mergeEnvironment(input.env, dep.env); - return dep; -} - -function transformerResultToInput( - input: TransformerInput, - result: TransformerResult -): TransformerInput { - return { - filePath: input.filePath, - code: result.code || (result.output && result.output.code) || '', - ast: result.ast, - env: mergeEnvironment(input.env, result.env) - }; -} - -function mergeEnvironment(a: Environment, b: ?Environment): Environment { - return Object.assign({}, a, b); -} + asset.ast = null; + asset.code = ''; -function getNextContext( - context: TransformContext, - result: TransformerResult -): TransformContext { - return { - type: result.type, - generate: context.generate, - dependencies: context.dependencies.concat(result.dependencies || []), - connectedFiles: context.connectedFiles.concat(result.connectedFiles || []), - hash: context.hash, - meta: Object.assign({}, context.meta, result.meta) - }; + return asset; } async function checkCacheEntry(cacheEntry: CacheEntry): Promise { @@ -355,7 +251,7 @@ async function checkCacheEntry(cacheEntry: CacheEntry): Promise { return results.every(Boolean); } -async function checkCachedAssets(assets: Array): Promise { +async function checkCachedAssets(assets: Array): Promise { let results = await Promise.all( assets.map(asset => checkConnectedFiles(asset.connectedFiles)) ); diff --git a/packages/core/core/src/createDependency.js b/packages/core/core/src/createDependency.js new file mode 100644 index 00000000000..aa9cce9a0d9 --- /dev/null +++ b/packages/core/core/src/createDependency.js @@ -0,0 +1,14 @@ +// @flow +import type {DependencyOptions, Dependency, FilePath} from '@parcel/types'; +import md5 from '@parcel/utils/md5'; + +export default function createDependency( + opts: DependencyOptions, + sourcePath: FilePath +): Dependency { + return { + ...opts, + sourcePath, // TODO: get this from the graph? + id: md5(`${sourcePath}:${opts.moduleSpecifier}:${JSON.stringify(opts.env)}`) + }; +} diff --git a/packages/core/core/test/AssetGraph.test.js b/packages/core/core/test/AssetGraph.test.js index b50d177c970..e159628dda6 100644 --- a/packages/core/core/test/AssetGraph.test.js +++ b/packages/core/core/test/AssetGraph.test.js @@ -1,11 +1,8 @@ // @flow 'use strict'; import assert from 'assert'; -import AssetGraph, { - nodeFromFile, - nodeFromTransformerRequest, - nodeFromDep -} from '../src/AssetGraph'; +import AssetGraph, {nodeFromTransformerRequest} from '../src/AssetGraph'; +import createDependency from '../src/createDependency'; const DEFAULT_ENV = { context: 'browser', @@ -33,20 +30,24 @@ describe('AssetGraph', () => { assert(graph.nodes.has('/')); assert( graph.nodes.has( - nodeFromDep({ - sourcePath: '/index', - moduleSpecifier: './index1', - env: DEFAULT_ENV - }).id + createDependency( + { + moduleSpecifier: './index1', + env: DEFAULT_ENV + }, + '/index' + ).id ) ); assert( graph.nodes.has( - nodeFromDep({ - sourcePath: '/index', - moduleSpecifier: './index2', - env: DEFAULT_ENV - }).id + createDependency( + { + moduleSpecifier: './index2', + env: DEFAULT_ENV + }, + '/index' + ).id ) ); assert.deepEqual( @@ -54,19 +55,23 @@ describe('AssetGraph', () => { new Set([ { from: '/', - to: nodeFromDep({ - sourcePath: '/index', - moduleSpecifier: './index1', - env: DEFAULT_ENV - }).id + to: createDependency( + { + moduleSpecifier: './index1', + env: DEFAULT_ENV + }, + '/index' + ).id }, { from: '/', - to: nodeFromDep({ - sourcePath: '/index', - moduleSpecifier: './index2', - env: DEFAULT_ENV - }).id + to: createDependency( + { + moduleSpecifier: './index2', + env: DEFAULT_ENV + }, + '/index' + ).id } ]) ); @@ -80,18 +85,20 @@ describe('AssetGraph', () => { rootDir: '/' }); - let dep = { - sourcePath: '/index', - moduleSpecifier: './index', - env: DEFAULT_ENV - }; + let dep = createDependency( + { + moduleSpecifier: './index', + env: DEFAULT_ENV + }, + '/index' + ); let req = {filePath: '/index.js', env: DEFAULT_ENV}; graph.resolveDependency(dep, req); assert(graph.nodes.has(nodeFromTransformerRequest(req).id)); assert( graph.hasEdge({ - from: nodeFromDep(dep).id, + from: dep.id, to: nodeFromTransformerRequest(req).id }) ); @@ -103,13 +110,13 @@ describe('AssetGraph', () => { assert(graph.nodes.has(nodeFromTransformerRequest(req2).id)); assert( graph.hasEdge({ - from: nodeFromDep(dep).id, + from: dep.id, to: nodeFromTransformerRequest(req2).id }) ); assert( !graph.hasEdge({ - from: nodeFromDep(dep).id, + from: dep.id, to: nodeFromTransformerRequest(req).id }) ); @@ -119,7 +126,7 @@ describe('AssetGraph', () => { assert(graph.nodes.has(nodeFromTransformerRequest(req2).id)); assert( graph.hasEdge({ - from: nodeFromDep(dep).id, + from: dep.id, to: nodeFromTransformerRequest(req2).id }) ); @@ -134,11 +141,13 @@ describe('AssetGraph', () => { rootDir: '/' }); - let dep = { - sourcePath: '/index', - moduleSpecifier: './index', - env: DEFAULT_ENV - }; + let dep = createDependency( + { + moduleSpecifier: './index', + env: DEFAULT_ENV + }, + '/index' + ); let filePath = '/index.js'; let req = {filePath, env: DEFAULT_ENV}; graph.resolveDependency(dep, req); @@ -149,7 +158,9 @@ describe('AssetGraph', () => { filePath, type: 'js', hash: '#1', - dependencies: [{sourcePath, moduleSpecifier: './utils'}], + dependencies: [ + createDependency({moduleSpecifier: './utils'}, sourcePath) + ], env: DEFAULT_ENV, output: {code: ''}, connectedFiles: [] @@ -159,7 +170,9 @@ describe('AssetGraph', () => { filePath, type: 'js', hash: '#2', - dependencies: [{sourcePath, moduleSpecifier: './styles'}], + dependencies: [ + createDependency({moduleSpecifier: './styles'}, sourcePath) + ], env: DEFAULT_ENV, output: {code: ''}, connectedFiles: [] @@ -188,8 +201,8 @@ describe('AssetGraph', () => { assert(graph.nodes.has('1')); assert(graph.nodes.has('2')); assert(graph.nodes.has('3')); - assert(graph.nodes.has(nodeFromDep(assets[0].dependencies[0]).id)); - assert(graph.nodes.has(nodeFromDep(assets[1].dependencies[0]).id)); + assert(graph.nodes.has(assets[0].dependencies[0].id)); + assert(graph.nodes.has(assets[1].dependencies[0].id)); assert(graph.nodes.has('/index.js')); assert( graph.hasEdge({ @@ -218,24 +231,24 @@ describe('AssetGraph', () => { assert( graph.hasEdge({ from: '1', - to: nodeFromDep(assets[0].dependencies[0]).id + to: assets[0].dependencies[0].id }) ); assert( graph.hasEdge({ from: '2', - to: nodeFromDep(assets[1].dependencies[0]).id + to: assets[1].dependencies[0].id }) ); assert(!graph.incompleteNodes.has(nodeFromTransformerRequest(req).id)); assert( graph.incompleteNodes.has( - nodeFromDep({sourcePath, moduleSpecifier: './utils'}).id + createDependency({moduleSpecifier: './utils'}, sourcePath).id ) ); assert( graph.incompleteNodes.has( - nodeFromDep({sourcePath, moduleSpecifier: './styles'}).id + createDependency({moduleSpecifier: './styles'}, sourcePath).id ) ); @@ -245,7 +258,9 @@ describe('AssetGraph', () => { filePath, type: 'js', hash: '#1', - dependencies: [{sourcePath, moduleSpecifier: './utils'}], + dependencies: [ + createDependency({moduleSpecifier: './utils'}, sourcePath) + ], env: DEFAULT_ENV, output: {code: ''}, connectedFiles: [] @@ -274,8 +289,8 @@ describe('AssetGraph', () => { assert(graph.nodes.has('1')); assert(graph.nodes.has('2')); assert(!graph.nodes.has('3')); - assert(graph.nodes.has(nodeFromDep(assets[0].dependencies[0]).id)); - assert(!graph.nodes.has(nodeFromDep(assets[1].dependencies[0]).id)); + assert(graph.nodes.has(assets[0].dependencies[0].id)); + assert(!graph.nodes.has(assets[1].dependencies[0].id)); assert( graph.hasEdge({ from: nodeFromTransformerRequest(req).id, @@ -303,24 +318,24 @@ describe('AssetGraph', () => { assert( graph.hasEdge({ from: '1', - to: nodeFromDep(assets[0].dependencies[0]).id + to: assets[0].dependencies[0].id }) ); assert( !graph.hasEdge({ from: '2', - to: nodeFromDep(assets[1].dependencies[0]).id + to: assets[1].dependencies[0].id }) ); assert(!graph.incompleteNodes.has(nodeFromTransformerRequest(req).id)); assert( graph.incompleteNodes.has( - nodeFromDep({sourcePath, moduleSpecifier: './utils'}).id + createDependency({moduleSpecifier: './utils'}, sourcePath).id ) ); assert( !graph.incompleteNodes.has( - nodeFromDep({sourcePath, moduleSpecifier: './styles'}).id + createDependency({moduleSpecifier: './styles'}, sourcePath).id ) ); }); @@ -333,7 +348,10 @@ describe('AssetGraph', () => { rootDir: '/' }); - let dep = {sourcePath: '/', moduleSpecifier: './index', env: DEFAULT_ENV}; + let dep = createDependency( + {moduleSpecifier: './index', env: DEFAULT_ENV}, + '/' + ); let filePath = '/index.js'; let req = {filePath, env: DEFAULT_ENV}; graph.resolveDependency(dep, req); @@ -344,7 +362,9 @@ describe('AssetGraph', () => { filePath, type: 'js', hash: '#1', - dependencies: [{sourcePath, moduleSpecifier: './utils'}], + dependencies: [ + createDependency({moduleSpecifier: './utils'}, sourcePath) + ], env: DEFAULT_ENV, output: {code: ''}, connectedFiles: [ diff --git a/packages/core/types/index.js b/packages/core/types/index.js index a6ad2fa387e..9bc5002b9d9 100644 --- a/packages/core/types/index.js +++ b/packages/core/types/index.js @@ -85,8 +85,7 @@ export type SourceLocation = { end: {line: number, column: number} }; -export type Dependency = { - sourcePath: FilePath, +export type DependencyOptions = { moduleSpecifier: ModuleSpecifier, isAsync?: boolean, isEntry?: boolean, @@ -98,6 +97,16 @@ export type Dependency = { meta?: JSONObject }; +export type Dependency = { + ...DependencyOptions, + id: string, + env: Environment, + + // TODO: get these from graph instead of storing them on dependencies + sourcePath: FilePath, + resolvedPath?: FilePath +}; + export type File = { filePath: FilePath, hash?: string @@ -108,17 +117,24 @@ export type TransformerRequest = { env: Environment }; -export type Asset = { - id: string, - filePath: FilePath, - type: string, - hash: string, - output: AssetOutput, - dependencies: Array, - connectedFiles: Array, - env: Environment, - meta?: JSONObject -}; +export interface Asset { + id: string; + hash: string; + filePath: FilePath; + type: string; + code: string; + ast: ?AST; + dependencies: Array; + connectedFiles: Array; + output: AssetOutput; + env: Environment; + meta: JSONObject; + + getConfig(filePaths: Array): Async; + getPackage(): Async; + addDependency(dep: DependencyOptions): string; + createChildAsset(result: TransformerResult): Asset; +} export type AssetOutput = { code: string, @@ -129,27 +145,18 @@ export type AssetOutput = { export type AST = { type: string, version: string, - program: JSONObject + program: any }; export type Config = JSONObject; export type SourceMap = JSONObject; export type Blob = string | Buffer; -export type TransformerInput = { - filePath: FilePath, - code: string, - ast: ?AST, - env: Environment -}; - -export type TransformerOutput = {}; - export type TransformerResult = { type: string, code?: string, ast?: ?AST, - dependencies?: Array, + dependencies?: Array, connectedFiles?: Array, output?: AssetOutput, env?: Environment, @@ -164,20 +171,16 @@ export type ConfigOutput = { type Async = T | Promise; export type Transformer = { - getConfig?: (filePath: FilePath, opts: CLIOptions) => Async, + getConfig?: (asset: Asset, opts: CLIOptions) => Async, canReuseAST?: (ast: AST, opts: CLIOptions) => boolean, - parse?: ( - asset: TransformerInput, - config: ?Config, - opts: CLIOptions - ) => Async, + parse?: (asset: Asset, config: ?Config, opts: CLIOptions) => Async, transform( - asset: TransformerInput, + asset: Asset, config: ?Config, opts: CLIOptions - ): Async>, + ): Async>, generate?: ( - asset: TransformerInput, + asset: Asset, config: ?Config, opts: CLIOptions ) => Async, diff --git a/packages/packagers/js/src/JSPackager.js b/packages/packagers/js/src/JSPackager.js index d9350e10e5f..5e25d358b89 100644 --- a/packages/packagers/js/src/JSPackager.js +++ b/packages/packagers/js/src/JSPackager.js @@ -19,7 +19,9 @@ export default new Packager({ let resolvedAsset = bundle.assets.find( a => a.filePath === dep.resolvedPath ); - deps[dep.moduleSpecifier] = resolvedAsset.id; + if (resolvedAsset) { + deps[dep.moduleSpecifier] = resolvedAsset.id; + } } let wrapped = i === 0 ? '' : ','; diff --git a/packages/transformers/js/src/JSTransformer.js b/packages/transformers/js/src/JSTransformer.js index e40d573b123..84d19877872 100644 --- a/packages/transformers/js/src/JSTransformer.js +++ b/packages/transformers/js/src/JSTransformer.js @@ -60,38 +60,22 @@ export default new Transformer({ async transform(asset, config, options) { if (!asset.ast) { - return [ - { - type: 'js', - code: asset.code, - ast: asset.ast - } - ]; + return [asset]; } - let module = { - type: 'js', - filePath: asset.filePath, - dependencies: [], - connectedFiles: [], - code: asset.code, - ast: asset.ast, - env: asset.env - }; - // Collect dependencies if (canHaveDependencies(asset.code)) { - walk.ancestor(module.ast.program, collectDependencies, module); + walk.ancestor(asset.ast.program, collectDependencies, asset); } if (asset.env.context === 'browser') { // Inline environment variables if (ENV_RE.test(asset.code)) { - walk.simple(module.ast.program, envVisitor, module); + walk.simple(asset.ast.program, envVisitor, asset); } // Inline fs calls - let fsDep = module.dependencies.find(dep => dep.moduleSpecifier === 'fs'); + let fsDep = asset.dependencies.find(dep => dep.moduleSpecifier === 'fs'); if (fsDep && FS_RE.test(asset.code)) { // Check if we should ignore fs calls // See https://github.com/defunctzombie/node-browser-resolve#skip @@ -100,18 +84,18 @@ export default new Transformer({ let ignore; if (!ignore) { - traverse(module.ast.program, fsVisitor, null, module); + traverse(asset.ast.program, fsVisitor, null, asset); } } // Insert node globals if (GLOBAL_RE.test(asset.code)) { - walk.ancestor(module.ast.program, insertGlobals, module); + walk.ancestor(asset.ast.program, insertGlobals, asset); } } // Do some transforms - return [module]; + return [asset]; }, async generate(module, config, options) { diff --git a/packages/transformers/js/src/visitors/dependencies.js b/packages/transformers/js/src/visitors/dependencies.js index 4a61945798d..4e9b798ae27 100644 --- a/packages/transformers/js/src/visitors/dependencies.js +++ b/packages/transformers/js/src/visitors/dependencies.js @@ -53,7 +53,7 @@ export default { types.isStringLiteral(args[0]); if (isDynamicImport) { - asset.dependencies.push({moduleSpecifier: '_bundle_loader'}); + asset.addDependency({moduleSpecifier: '_bundle_loader'}); addDependency(asset, args[0], {isAsync: true}); node.callee = requireTemplate().expression; @@ -71,7 +71,7 @@ export default { // https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle#avoid_changing_the_url_of_your_service_worker_script addURLDependency(asset, args[0], { isEntry: true, - context: 'serviceworker' + context: 'service-worker' }); return; } @@ -87,7 +87,7 @@ export default { types.isStringLiteral(args[0]); if (isWebWorker) { - addURLDependency(asset, args[0], {context: 'webworker'}); + addURLDependency(asset, args[0], {context: 'web-worker'}); return; } } @@ -176,7 +176,7 @@ function addDependency(asset, node, opts = {}) { } } - asset.dependencies.push( + asset.addDependency( Object.assign( { moduleSpecifier: node.value, @@ -190,10 +190,6 @@ function addDependency(asset, node, opts = {}) { function addURLDependency(asset, node, opts = {}) { opts.loc = node.loc && node.loc.start; - let assetPath = asset.addURLDependency(node.value, opts); - if (!isURL(assetPath)) { - assetPath = urlJoin(asset.options.publicURL, assetPath); - } - node.value = assetPath; + node.value = asset.addDependency({moduleSpecifier: node.value, ...opts}); asset.ast.isDirty = true; } diff --git a/packages/transformers/terser/src/TerserTransformer.js b/packages/transformers/terser/src/TerserTransformer.js index dbab63c21cc..4e0b5c54142 100644 --- a/packages/transformers/terser/src/TerserTransformer.js +++ b/packages/transformers/terser/src/TerserTransformer.js @@ -1,7 +1,6 @@ // @flow import {minify} from 'terser'; import {Transformer} from '@parcel/plugin'; -import config from '@parcel/utils/config'; // TODO: extract SourceMap from parcel-bundler ? // Just using an empty class skeleton for now so that linting doesn't fail @@ -11,8 +10,8 @@ class SourceMap { } export default new Transformer({ - async getConfig(filePath /* , options */) { - return config.load(filePath, [ + async getConfig(asset) { + return asset.getConfig([ '.terserrc', '.uglifyrc', '.uglifyrc.js', @@ -20,7 +19,7 @@ export default new Transformer({ ]); }, - async transform(module, config, options) { + async transform(asset, config, options) { let terserOptions = { warnings: true, mangle: { @@ -55,10 +54,13 @@ export default new Transformer({ terserOptions = Object.assign({}, terserOptions, config); } - let result = minify(module.code, terserOptions); + let result = minify(asset.code, terserOptions); - if (sourceMap && module.map) { - sourceMap = await new SourceMap().extendSourceMap(module.map, sourceMap); + if (sourceMap && asset.output.map) { + sourceMap = await new SourceMap().extendSourceMap( + asset.output.map, + sourceMap + ); } if (result.error) {