From da127ffea0a2f46d71b942f95ea390f7813eaec9 Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Tue, 29 Dec 2015 20:36:46 -0500 Subject: [PATCH] replace classes with functions --- src/Bundle.js | 84 +++++++++++++-------------- src/Declaration.js | 130 +++++++++++++++++++++--------------------- src/ExternalModule.js | 26 ++++----- src/Module.js | 112 ++++++++++++++++++------------------ src/Statement.js | 79 +++++++++++++------------ src/ast/Scope.js | 38 ++++++------ src/utils/object.js | 8 +++ 7 files changed, 242 insertions(+), 235 deletions(-) diff --git a/src/Bundle.js b/src/Bundle.js index 950ae9c..456a418 100644 --- a/src/Bundle.js +++ b/src/Bundle.js @@ -1,7 +1,7 @@ import Promise from 'es6-promise/lib/es6-promise/promise.js'; import MagicString from 'magic-string'; import first from './utils/first.js'; -import { blank, keys } from './utils/object.js'; +import { assign, blank, keys } from './utils/object.js'; import Module from './Module.js'; import ExternalModule from './ExternalModule.js'; import finalisers from './finalisers/index.js'; @@ -15,52 +15,52 @@ import collapseSourcemaps from './utils/collapseSourcemaps.js'; import callIfFunction from './utils/callIfFunction.js'; import { isRelative } from './utils/path.js'; -export default class Bundle { - constructor ( options ) { - this.plugins = ensureArray( options.plugins ); +export default function Bundle ( options ) { + this.plugins = ensureArray( options.plugins ); - this.plugins.forEach( plugin => { - if ( plugin.options ) { - options = plugin.options( options ) || options; - } - }); + this.plugins.forEach( plugin => { + if ( plugin.options ) { + options = plugin.options( options ) || options; + } + }); - this.entry = options.entry; - this.entryModule = null; + this.entry = options.entry; + this.entryModule = null; - this.resolveId = first( - this.plugins - .map( plugin => plugin.resolveId ) - .filter( Boolean ) - .concat( resolveId ) - ); + this.resolveId = first( + this.plugins + .map( plugin => plugin.resolveId ) + .filter( Boolean ) + .concat( resolveId ) + ); - this.load = first( - this.plugins - .map( plugin => plugin.load ) - .filter( Boolean ) - .concat( load ) - ); + this.load = first( + this.plugins + .map( plugin => plugin.load ) + .filter( Boolean ) + .concat( load ) + ); - this.transformers = this.plugins - .map( plugin => plugin.transform ) - .filter( Boolean ); + this.transformers = this.plugins + .map( plugin => plugin.transform ) + .filter( Boolean ); - this.moduleById = blank(); - this.modules = []; + this.moduleById = blank(); + this.modules = []; - this.externalModules = []; - this.internalNamespaces = []; + this.externalModules = []; + this.internalNamespaces = []; - this.assumedGlobals = blank(); + this.assumedGlobals = blank(); - this.external = options.external || []; - this.onwarn = options.onwarn || makeOnwarn(); + this.external = options.external || []; + this.onwarn = options.onwarn || makeOnwarn(); - // TODO strictly speaking, this only applies with non-ES6, non-default-only bundles - [ 'module', 'exports' ].forEach( global => this.assumedGlobals[ global ] = true ); - } + // TODO strictly speaking, this only applies with non-ES6, non-default-only bundles + [ 'module', 'exports' ].forEach( global => this.assumedGlobals[ global ] = true ); +} +assign( Bundle.prototype, { build () { // Phase 1 – discovery. We load the entry module and find which // modules it imports, and import those, until we have all @@ -103,7 +103,7 @@ export default class Bundle { this.orderedModules = this.sort(); this.deconflict(); }); - } + }, deconflict () { let used = blank(); @@ -135,7 +135,7 @@ export default class Bundle { declaration.name = getSafeName( declaration.name ); }); }); - } + }, fetchModule ( id, importer ) { // short-circuit cycles @@ -161,7 +161,7 @@ export default class Bundle { return this.fetchAllDependencies( module ).then( () => module ); }); - } + }, fetchAllDependencies ( module ) { const promises = module.dependencies.map( source => { @@ -191,7 +191,7 @@ export default class Bundle { }); return Promise.all( promises ); - } + }, render ( options = {} ) { const format = options.format || 'es6'; @@ -258,7 +258,7 @@ export default class Bundle { } return { code, map }; - } + }, sort () { let seen = {}; @@ -340,4 +340,4 @@ export default class Bundle { return ordered; } -} +}); diff --git a/src/Declaration.js b/src/Declaration.js index f7076f0..f159d64 100644 --- a/src/Declaration.js +++ b/src/Declaration.js @@ -1,45 +1,45 @@ -import { blank, keys } from './utils/object.js'; +import { assign, blank, keys } from './utils/object.js'; import run from './utils/run.js'; -export default class Declaration { - constructor ( node, isParam, statement ) { - if ( node ) { - if ( node.type === 'FunctionDeclaration' ) { - this.isFunctionDeclaration = true; - this.functionNode = node; - } else if ( node.type === 'VariableDeclarator' && node.init && /FunctionExpression/.test( node.init.type ) ) { - this.isFunctionDeclaration = true; - this.functionNode = node.init; - } +export default function Declaration ( node, isParam, statement ) { + if ( node ) { + if ( node.type === 'FunctionDeclaration' ) { + this.isFunctionDeclaration = true; + this.functionNode = node; + } else if ( node.type === 'VariableDeclarator' && node.init && /FunctionExpression/.test( node.init.type ) ) { + this.isFunctionDeclaration = true; + this.functionNode = node.init; } + } - this.statement = statement; - this.name = null; - this.isParam = isParam; + this.statement = statement; + this.name = null; + this.isParam = isParam; - this.isReassigned = false; - this.aliases = []; + this.isReassigned = false; + this.aliases = []; - this.isUsed = false; - } + this.isUsed = false; +} +assign( Declaration.prototype, { addAlias ( declaration ) { this.aliases.push( declaration ); - } + }, addReference ( reference ) { reference.declaration = this; this.name = reference.name; // TODO handle differences of opinion if ( reference.isReassignment ) this.isReassigned = true; - } + }, render ( es6 ) { if ( es6 ) return this.name; if ( !this.isReassigned || !this.isExported ) return this.name; return `exports.${this.name}`; - } + }, run ( strongDependencies ) { if ( this.tested ) return this.hasSideEffects; @@ -52,7 +52,7 @@ export default class Declaration { } return this.hasSideEffects; - } + }, use () { if ( this.isUsed ) return; @@ -62,22 +62,22 @@ export default class Declaration { this.aliases.forEach( alias => alias.use() ); } -} +}); -export class SyntheticDefaultDeclaration { - constructor ( node, statement, name ) { - this.node = node; - this.statement = statement; - this.name = name; +export function SyntheticDefaultDeclaration ( node, statement, name ) { + this.node = node; + this.statement = statement; + this.name = name; - this.original = null; - this.isExported = false; - this.aliases = []; - } + this.original = null; + this.isExported = false; + this.aliases = []; +} +assign( SyntheticDefaultDeclaration.prototype, { addAlias ( declaration ) { this.aliases.push( declaration ); - } + }, addReference ( reference ) { // Bind the reference to `this` declaration. @@ -87,17 +87,17 @@ export class SyntheticDefaultDeclaration { if ( reference.name === 'default' ) return; this.name = reference.name; - } + }, bind ( declaration ) { this.original = declaration; - } + }, render () { return !this.original || this.original.isReassigned ? this.name : this.original.render(); - } + }, run ( strongDependencies ) { if ( this.original ) { @@ -107,7 +107,7 @@ export class SyntheticDefaultDeclaration { if ( /FunctionExpression/.test( this.node.declaration.type ) ) { return run( this.node.declaration.body, this.statement.scope, this.statement, strongDependencies, false ); } - } + }, use () { this.isUsed = true; @@ -117,25 +117,25 @@ export class SyntheticDefaultDeclaration { this.aliases.forEach( alias => alias.use() ); } -} +}); -export class SyntheticNamespaceDeclaration { - constructor ( module ) { - this.module = module; - this.name = null; +export function SyntheticNamespaceDeclaration ( module ) { + this.module = module; + this.name = null; - this.needsNamespaceBlock = false; - this.aliases = []; + this.needsNamespaceBlock = false; + this.aliases = []; - this.originals = blank(); - module.getExports().forEach( name => { - this.originals[ name ] = module.traceExport( name ); - }); - } + this.originals = blank(); + module.getExports().forEach( name => { + this.originals[ name ] = module.traceExport( name ); + }); +} +assign( SyntheticNamespaceDeclaration.prototype, { addAlias ( declaration ) { this.aliases.push( declaration ); - } + }, addReference ( reference ) { // if we have e.g. `foo.bar`, we can optimise @@ -168,7 +168,7 @@ export class SyntheticNamespaceDeclaration { reference.declaration = this; this.name = reference.name; - } + }, renderBlock ( indentString ) { const members = keys( this.originals ).map( name => { @@ -182,11 +182,11 @@ export class SyntheticNamespaceDeclaration { }); return `var ${this.render()} = Object.freeze({\n${members.join( ',\n' )}\n});\n\n`; - } + }, render () { return this.name; - } + }, use () { keys( this.originals ).forEach( name => { @@ -195,18 +195,18 @@ export class SyntheticNamespaceDeclaration { this.aliases.forEach( alias => alias.use() ); } -} +}); -export class ExternalDeclaration { - constructor ( module, name ) { - this.module = module; - this.name = name; - this.isExternal = true; - } +export function ExternalDeclaration ( module, name ) { + this.module = module; + this.name = name; + this.isExternal = true; +} +assign( ExternalDeclaration.prototype, { addAlias () { // noop - } + }, addReference ( reference ) { reference.declaration = this; @@ -214,7 +214,7 @@ export class ExternalDeclaration { if ( this.name === 'default' || this.name === '*' ) { this.module.suggestName( reference.name ); } - } + }, render ( es6 ) { if ( this.name === '*' ) { @@ -228,13 +228,13 @@ export class ExternalDeclaration { } return es6 ? this.name : `${this.module.name}.${this.name}`; - } + }, run () { return true; - } + }, use () { // noop? } -} +}); diff --git a/src/ExternalModule.js b/src/ExternalModule.js index 3113a5d..7d1bc77 100644 --- a/src/ExternalModule.js +++ b/src/ExternalModule.js @@ -1,21 +1,21 @@ -import { blank } from './utils/object.js'; +import { assign, blank } from './utils/object.js'; import makeLegalIdentifier from './utils/makeLegalIdentifier.js'; import { ExternalDeclaration } from './Declaration.js'; -export default class ExternalModule { - constructor ( id ) { - this.id = id; - this.name = makeLegalIdentifier( id ); +export default function ExternalModule ( id ) { + this.id = id; + this.name = makeLegalIdentifier( id ); - this.nameSuggestions = blank(); - this.mostCommonSuggestion = 0; + this.nameSuggestions = blank(); + this.mostCommonSuggestion = 0; - this.isExternal = true; - this.declarations = blank(); + this.isExternal = true; + this.declarations = blank(); - this.exportsNames = false; - } + this.exportsNames = false; +} +assign( ExternalModule.prototype, { suggestName ( name ) { if ( !this.nameSuggestions[ name ] ) this.nameSuggestions[ name ] = 0; this.nameSuggestions[ name ] += 1; @@ -24,7 +24,7 @@ export default class ExternalModule { this.mostCommonSuggestion = this.nameSuggestions[ name ]; this.name = name; } - } + }, traceExport ( name ) { if ( name !== 'default' && name !== '*' ) { @@ -35,4 +35,4 @@ export default class ExternalModule { this.declarations[ name ] = new ExternalDeclaration( this, name ) ); } -} +}); diff --git a/src/Module.js b/src/Module.js index e1965ab..05ddf0a 100644 --- a/src/Module.js +++ b/src/Module.js @@ -2,7 +2,7 @@ import { parse } from 'acorn/src/index.js'; import MagicString from 'magic-string'; import { walk } from 'estree-walker'; import Statement from './Statement.js'; -import { blank, keys } from './utils/object.js'; +import { assign, blank, keys } from './utils/object.js'; import { basename, extname } from './utils/path.js'; import getLocation from './utils/getLocation.js'; import makeLegalIdentifier from './utils/makeLegalIdentifier.js'; @@ -11,50 +11,50 @@ import { SyntheticDefaultDeclaration, SyntheticNamespaceDeclaration } from './De import { isFalsy, isTruthy } from './ast/conditions.js'; import { emptyBlockStatement } from './ast/create.js'; -export default class Module { - constructor ({ id, code, originalCode, ast, sourceMapChain, bundle }) { - this.code = code; - this.originalCode = originalCode; - this.sourceMapChain = sourceMapChain; - - this.bundle = bundle; - this.id = id; - - // all dependencies - this.dependencies = []; - this.resolvedIds = blank(); - - // imports and exports, indexed by local name - this.imports = blank(); - this.exports = blank(); - this.reexports = blank(); - - this.exportAllSources = []; - this.exportAllModules = null; - - // By default, `id` is the filename. Custom resolvers and loaders - // can change that, but it makes sense to use it for the source filename - this.magicString = new MagicString( code, { - filename: id, - indentExclusionRanges: [] - }); - - // remove existing sourceMappingURL comments - const pattern = new RegExp( `\\/\\/#\\s+${SOURCEMAPPING_URL}=.+\\n?`, 'g' ); - let match; - while ( match = pattern.exec( code ) ) { - this.magicString.remove( match.index, match.index + match[0].length ); - } +export default function Module ({ id, code, originalCode, ast, sourceMapChain, bundle }) { + this.code = code; + this.originalCode = originalCode; + this.sourceMapChain = sourceMapChain; + + this.bundle = bundle; + this.id = id; + + // all dependencies + this.dependencies = []; + this.resolvedIds = blank(); + + // imports and exports, indexed by local name + this.imports = blank(); + this.exports = blank(); + this.reexports = blank(); + + this.exportAllSources = []; + this.exportAllModules = null; + + // By default, `id` is the filename. Custom resolvers and loaders + // can change that, but it makes sense to use it for the source filename + this.magicString = new MagicString( code, { + filename: id, + indentExclusionRanges: [] + }); + + // remove existing sourceMappingURL comments + const pattern = new RegExp( `\\/\\/#\\s+${SOURCEMAPPING_URL}=.+\\n?`, 'g' ); + let match; + while ( match = pattern.exec( code ) ) { + this.magicString.remove( match.index, match.index + match[0].length ); + } - this.comments = []; - this.statements = this.parse( ast ); + this.comments = []; + this.statements = this.parse( ast ); - this.declarations = blank(); - this.analyse(); + this.declarations = blank(); + this.analyse(); - this.strongDependencies = []; - } + this.strongDependencies = []; +} +assign( Module.prototype, { addExport ( statement ) { const node = statement.node; const source = node.source && node.source.value; @@ -127,7 +127,7 @@ export default class Module { this.exports[ name ] = { localName: name }; } } - } + }, addImport ( statement ) { const node = statement.node; @@ -151,7 +151,7 @@ export default class Module { const name = isDefault ? 'default' : isNamespace ? '*' : specifier.imported.name; this.imports[ localName ] = { source, name, module: null }; }); - } + }, analyse () { // discover this module's imports and exports @@ -165,14 +165,14 @@ export default class Module { this.declarations[ name ] = declaration; }); }); - } + }, basename () { const base = basename( this.id ); const ext = extname( this.id ); return makeLegalIdentifier( ext ? base.slice( 0, -ext.length ) : base ); - } + }, bindAliases () { keys( this.declarations ).forEach( name => { @@ -193,7 +193,7 @@ export default class Module { if ( otherDeclaration ) otherDeclaration.addAlias( declaration ); }); }); - } + }, bindImportSpecifiers () { [ this.imports, this.reexports ].forEach( specifiers => { @@ -209,7 +209,7 @@ export default class Module { const id = this.resolvedIds[ source ]; return this.bundle.moduleById[ id ]; }); - } + }, bindReferences () { if ( this.declarations.default ) { @@ -238,7 +238,7 @@ export default class Module { } }); }); - } + }, consolidateDependencies () { let strongDependencies = []; @@ -258,7 +258,7 @@ export default class Module { .filter( module => module !== this ); return { strongDependencies, weakDependencies }; - } + }, getExports () { let exports = blank(); @@ -278,7 +278,7 @@ export default class Module { }); return keys( exports ); - } + }, namespace () { if ( !this.declarations['*'] ) { @@ -286,7 +286,7 @@ export default class Module { } return this.declarations['*']; - } + }, parse ( ast ) { // The ast can be supplied programmatically (but usually won't be) @@ -417,7 +417,7 @@ export default class Module { } return statements; - } + }, render ( es6 ) { let magicString = this.magicString.clone(); @@ -557,7 +557,7 @@ export default class Module { } return magicString.trim(); - } + }, run ( safe ) { let marked = false; @@ -567,7 +567,7 @@ export default class Module { }); return marked; - } + }, trace ( name ) { if ( name in this.declarations ) return this.declarations[ name ]; @@ -586,7 +586,7 @@ export default class Module { } return null; - } + }, traceExport ( name ) { // export { foo } from './other.js' @@ -616,4 +616,4 @@ export default class Module { if ( declaration ) return declaration; } } -} +}); diff --git a/src/Statement.js b/src/Statement.js index 9d75f13..7b49ce5 100644 --- a/src/Statement.js +++ b/src/Statement.js @@ -6,55 +6,54 @@ import isFunctionDeclaration from './ast/isFunctionDeclaration.js'; import isReference from './ast/isReference.js'; import getLocation from './utils/getLocation.js'; import run from './utils/run.js'; +import { assign } from './utils/object.js'; -class Reference { - constructor ( node, scope, statement ) { - this.node = node; - this.scope = scope; - this.statement = statement; +function Reference ( node, scope, statement ) { + this.node = node; + this.scope = scope; + this.statement = statement; - this.declaration = null; // bound later + this.declaration = null; // bound later - this.parts = []; + this.parts = []; - let root = node; - while ( root.type === 'MemberExpression' ) { - this.parts.unshift( root.property.name ); - root = root.object; - } + let root = node; + while ( root.type === 'MemberExpression' ) { + this.parts.unshift( root.property.name ); + root = root.object; + } - this.name = root.name; + this.name = root.name; - this.start = node.start; - this.end = node.start + this.name.length; // can be overridden in the case of namespace members - this.rewritten = false; - } + this.start = node.start; + this.end = node.start + this.name.length; // can be overridden in the case of namespace members + this.rewritten = false; } -export default class Statement { - constructor ( node, module, start, end ) { - this.node = node; - this.module = module; - this.start = start; - this.end = end; - this.next = null; // filled in later +export default function Statement ( node, module, start, end ) { + this.node = node; + this.module = module; + this.start = start; + this.end = end; + this.next = null; // filled in later - this.scope = new Scope({ statement: this }); + this.scope = new Scope({ statement: this }); - this.references = []; - this.stringLiteralRanges = []; + this.references = []; + this.stringLiteralRanges = []; - this.isIncluded = false; - this.ran = false; + this.isIncluded = false; + this.ran = false; - this.isImportDeclaration = node.type === 'ImportDeclaration'; - this.isExportDeclaration = /^Export/.test( node.type ); - this.isReexportDeclaration = this.isExportDeclaration && !!node.source; + this.isImportDeclaration = node.type === 'ImportDeclaration'; + this.isExportDeclaration = /^Export/.test( node.type ); + this.isReexportDeclaration = this.isExportDeclaration && !!node.source; - this.isFunctionDeclaration = isFunctionDeclaration( node ) || - this.isExportDeclaration && isFunctionDeclaration( node.declaration ); - } + this.isFunctionDeclaration = isFunctionDeclaration( node ) || + this.isExportDeclaration && isFunctionDeclaration( node.declaration ); +} +assign( Statement.prototype, { firstPass () { if ( this.isImportDeclaration ) return; // nothing to analyse @@ -142,7 +141,7 @@ export default class Statement { if ( /Function/.test( node.type ) ) readDepth -= 1; } }); - } + }, mark () { if ( this.isIncluded ) return; // prevent infinite loops @@ -151,7 +150,7 @@ export default class Statement { this.references.forEach( reference => { if ( reference.declaration ) reference.declaration.use(); }); - } + }, run ( strongDependencies, safe ) { if ( ( this.ran && this.isIncluded ) || this.isImportDeclaration || this.isFunctionDeclaration ) return; @@ -161,13 +160,13 @@ export default class Statement { this.mark(); return true; } - } + }, source () { return this.module.source.slice( this.start, this.end ); - } + }, toString () { return this.module.magicString.slice( this.start, this.end ); } -} +}); diff --git a/src/ast/Scope.js b/src/ast/Scope.js index 508bf31..a7217b7 100644 --- a/src/ast/Scope.js +++ b/src/ast/Scope.js @@ -1,4 +1,4 @@ -import { blank, keys } from '../utils/object.js'; +import { assign, blank, keys } from '../utils/object.js'; import Declaration from '../Declaration.js'; const extractors = { @@ -34,26 +34,26 @@ function extractNames ( param ) { return names; } -export default class Scope { - constructor ( options ) { - options = options || {}; +export default function Scope ( options ) { + options = options || {}; - this.parent = options.parent; - this.statement = options.statement || this.parent.statement; - this.isBlockScope = !!options.block; - this.isTopLevel = !this.parent || ( this.parent.isTopLevel && this.isBlockScope ); + this.parent = options.parent; + this.statement = options.statement || this.parent.statement; + this.isBlockScope = !!options.block; + this.isTopLevel = !this.parent || ( this.parent.isTopLevel && this.isBlockScope ); - this.declarations = blank(); + this.declarations = blank(); - if ( options.params ) { - options.params.forEach( param => { - extractNames( param ).forEach( name => { - this.declarations[ name ] = new Declaration( param, true, this.statement ); - }); + if ( options.params ) { + options.params.forEach( param => { + extractNames( param ).forEach( name => { + this.declarations[ name ] = new Declaration( param, true, this.statement ); }); - } + }); } +} +assign( Scope.prototype, { addDeclaration ( node, isBlockDeclaration, isVar ) { if ( !isBlockDeclaration && this.isBlockScope ) { // it's a `var` or function node, and this @@ -64,21 +64,21 @@ export default class Scope { this.declarations[ name ] = new Declaration( node, false, this.statement ); }); } - } + }, contains ( name ) { return this.declarations[ name ] || ( this.parent ? this.parent.contains( name ) : false ); - } + }, eachDeclaration ( fn ) { keys( this.declarations ).forEach( key => { fn( key, this.declarations[ key ] ); }); - } + }, findDeclaration ( name ) { return this.declarations[ name ] || ( this.parent && this.parent.findDeclaration( name ) ); } -} +}); diff --git a/src/utils/object.js b/src/utils/object.js index a11ea25..beff197 100644 --- a/src/utils/object.js +++ b/src/utils/object.js @@ -1,5 +1,13 @@ export const keys = Object.keys; +export function assign ( source, target ) { + keys( target ).forEach( key => { + source[ key ] = target[ key ]; + }); + + return target; +} + export function blank () { return Object.create( null ); }