Browse Source

replace classes with functions

gh-384
Rich-Harris 9 years ago
parent
commit
da127ffea0
  1. 84
      src/Bundle.js
  2. 130
      src/Declaration.js
  3. 26
      src/ExternalModule.js
  4. 112
      src/Module.js
  5. 79
      src/Statement.js
  6. 38
      src/ast/Scope.js
  7. 8
      src/utils/object.js

84
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;
}
}
});

130
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?
}
}
});

26
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 )
);
}
}
});

112
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;
}
}
}
});

79
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 );
}
}
});

38
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 ) );
}
}
});

8
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 );
}

Loading…
Cancel
Save