Browse Source

replace names as necessary

contingency-plan
Rich-Harris 10 years ago
parent
commit
9ec400a2a0
  1. 60
      src/Bundle/index.js
  2. 73
      src/Module/index.js
  3. 18
      src/ast/analyse.js
  4. 13
      src/rollup.js
  5. 55
      src/utils/replaceIdentifiers.js
  6. 3
      test/samples/import-default-as-other/_config.js
  7. 3
      test/samples/import-default-as-other/foo.js
  8. 2
      test/samples/import-default-as-other/main.js
  9. 3
      test/samples/import-named-function-as-other/_config.js
  10. 3
      test/samples/import-named-function-as-other/foo.js
  11. 2
      test/samples/import-named-function-as-other/main.js

60
src/Bundle/index.js

@ -91,63 +91,9 @@ export default class Bundle {
return {
code: magicString.toString(),
map: null // TODO use magicString.generateMap()
};
// try {
// const code = generate({
// type: 'Program',
// body: this.body
// });
// const code = this.body.map( statement => statement._source.toString ).join( '\n' );
// return {
// code,
// map: null // TODO...
// };
// } catch ( err ) {
// // probably an escodegen error
// console.log( 'this.body', this.body );
// console.log( 'err.stack', err.stack );
// throw err;
// }
}
getName ( module, localName ) {
if ( !hasOwnProp.call( this.names, module.path ) ) {
this.names[ module.path ] = {};
}
const moduleNames = this.names[ module.path ];
if ( !moduleNames ) {
throw new Error( `Could not get name for ${module.relativePath}:${localName}` );
}
return moduleNames[ localName ];
}
suggestName ( module, localName, globalName ) {
if ( !hasOwnProp.call( this.names, module.path ) ) {
this.names[ module.path ] = {};
}
const moduleNames = this.names[ module.path ];
if ( !hasOwnProp.call( moduleNames, globalName ) ) {
const relativePathParts = module.relativePath.split( sep );
map: magicString.generateMap({
while ( hasOwnProp.call( this.usedNames, globalName ) && relativePathParts.length ) {
globalName = relativePathParts.pop() + `__${globalName}`;
}
while ( hasOwnProp.call( this.usedNames, globalName ) ) {
globalName = `_${globalName}`;
}
this.usedNames[ globalName ] = true;
moduleNames[ localName ] = globalName;
}
})
};
}
}

73
src/Module/index.js

@ -5,6 +5,7 @@ import MagicString from 'magic-string';
import analyse from '../ast/analyse';
import { hasOwnProp } from '../utils/object';
import { sequence } from '../utils/promise';
import replaceIdentifiers from '../utils/replaceIdentifiers';
const emptyArrayPromise = Promise.resolve([]);
@ -20,9 +21,9 @@ export default class Module {
sourceType: 'module'
});
console.log( '\nanalysing %s\n========', path );
analyse( this.ast, this.code );
console.log( '========\n\n' );
this.nameReplacements = {};
this.definitions = {};
this.definitionPromises = {};
@ -34,7 +35,11 @@ export default class Module {
});
Object.keys( statement._modifies ).forEach( name => {
this.modifications[ name ] = statement;
if ( !hasOwnProp.call( this.modifications, name ) ) {
this.modifications[ name ] = [];
}
this.modifications[ name ].push( statement );
});
});
@ -62,8 +67,8 @@ export default class Module {
this.exports.default = {
node,
localName: 'default',
name: isDeclaration ? node.declaration.id.name : null,
name: 'default',
localName: isDeclaration ? node.declaration.id.name : 'default',
isDeclaration
};
}
@ -128,7 +133,7 @@ export default class Module {
// we 'suggest' that the bundle use our local name for this import
// throughout the bundle. If that causes a conflict, we'll end up
// with something slightly different
this.bundle.suggestName( module, exportDeclaration.localName, importDeclaration.localName );
module.nameReplacements[ exportDeclaration.localName ] = importDeclaration.localName;
return module.define( exportDeclaration.localName );
});
@ -137,22 +142,27 @@ export default class Module {
// The definition is in this module
else if ( name === 'default' && this.exports.default.isDeclaration ) {
// We have something like `export default foo` - so we just start again,
// searching for `foo` instead of default
// searching for `foo` instead of default. First, sync up names
this.nameReplacements.default = this.exports.default.name;
promise = this.define( this.exports.default.name );
}
else {
let statement;
if ( !name ) {
console.log( new Error( 'no name' ).stack );
}
if ( name === 'default' ) {
// We have an expression, e.g. `export default 42`. We have
// to assign that expression to a variable
const name = this.bundle.getName( this, 'default' );
const replacement = this.nameReplacements.default;
statement = this.exports.default.node;
if ( !statement._imported ) {
statement._source.overwrite( statement.start, statement.declaration.start, `var ${name} = ` )
statement._source.overwrite( statement.start, statement.declaration.start, `var ${replacement} = ` )
}
}
@ -167,19 +177,34 @@ export default class Module {
if ( statement && !statement._imported ) {
const nodes = [];
promise = sequence( Object.keys( statement._dependsOn ), name => {
return this.define( name );
})
.then( definitions => {
definitions.forEach( definition => nodes.push.apply( nodes, definition ) );
})
.then( () => {
statement._imported = true;
nodes.push( statement );
})
.then( () => {
return nodes;
});
// replace identifiers, as necessary
replaceIdentifiers( statement, statement._source, this.nameReplacements );
const include = statement => {
if ( statement._imported ) return emptyArrayPromise;
const dependencies = Object.keys( statement._dependsOn );
return sequence( dependencies, name => this.define( name ) )
.then( definitions => {
definitions.forEach( definition => nodes.push.apply( nodes, definition ) );
})
.then( () => {
statement._imported = true;
nodes.push( statement );
const modifications = hasOwnProp.call( this.modifications, name ) && this.modifications[ name ];
if ( modifications ) {
return sequence( modifications, include );
}
})
.then( () => {
return nodes;
});
};
promise = include( statement );
}
}
@ -188,4 +213,8 @@ export default class Module {
return this.definitionPromises[ name ];
}
replaceName ( name, replacement ) {
this.nameReplacements[ name ] = replacement;
}
}

18
src/ast/analyse.js

@ -9,7 +9,7 @@ function isStatement ( node, parent ) {
node.type === 'FunctionDeclaration'; // TODO or any of the other various statement-ish things it could be
}
export default function analyse ( ast, code ) {
export default function analyse ( ast, magicString ) {
let scope = new Scope();
let topLevelStatements = [];
let currentTopLevelStatement;
@ -44,7 +44,7 @@ export default function analyse ( ast, code ) {
statement._imported = false;
// store the actual code, for easy regeneration
statement._source = code.snip( previous, statement.end );
statement._source = magicString.snip( previous, statement.end );
previous = statement.end;
currentTopLevelStatement = statement; // so we can attach scoping info
@ -53,6 +53,8 @@ export default function analyse ( ast, code ) {
enter ( node, parent ) {
let newScope;
magicString.addSourcemapLocation( node.start );
switch ( node.type ) {
case 'FunctionExpression':
case 'FunctionDeclaration':
@ -137,7 +139,19 @@ export default function analyse ( ast, code ) {
}
function checkForWrites ( node ) {
if ( node.type === 'AssignmentExpression' ) {
let assignee = node.left;
while ( assignee.type === 'MemberExpression' ) {
assignee = assignee.object;
}
if ( assignee.type !== 'Identifier' ) { // could be a ThisExpression
return;
}
statement._modifies[ assignee.name ] = true;
}
}
walk( statement, {

13
src/rollup.js

@ -1,6 +1,10 @@
import { basename } from 'path';
import { writeFile } from 'sander';
import Bundle from './Bundle';
let SOURCEMAPPING_URL = 'sourceMa';
SOURCEMAPPING_URL += 'ppingURL';
export function rollup ( entry, options = {} ) {
const bundle = new Bundle({
entry,
@ -11,9 +15,14 @@ export function rollup ( entry, options = {} ) {
return {
generate: options => bundle.generate( options ),
write: ( dest, options ) => {
const generated = bundle.generate( options );
let { code, map } = bundle.generate( options );
code += `\n//# ${SOURCEMAPPING_URL}=${basename( dest )}.map`;
return writeFile( dest, generated.code );
return Promise.all([
writeFile( dest, code ),
writeFile( dest + '.map', map.toString() )
]);
}
};
});

55
src/utils/replaceIdentifiers.js

@ -0,0 +1,55 @@
import walk from '../ast/walk';
import { hasOwnProp } from './object';
export default function replaceIdentifiers ( statement, snippet, names ) {
const replacementStack = [ names ];
const keys = Object.keys( names );
if ( keys.length === 0 ) {
return;
}
walk( statement, {
enter ( node, parent ) {
const scope = node._scope;
if ( scope ) {
let newNames = {};
let hasReplacements;
keys.forEach( key => {
if ( !~scope.names.indexOf( key ) ) {
newNames[ key ] = names[ key ];
hasReplacements = true;
}
});
if ( !hasReplacements ) {
return this.skip();
}
replacementStack.push( newNames );
}
if ( node.type === 'Identifier' && parent.type !== 'MemberExpression' ) {
let name = node.name;
while ( hasOwnProp.call( names, name ) && name !== names[ name ] ) {
name = names[ name ];
}
if ( name && name !== node.name ) {
snippet.overwrite( node.start, node.end, name );
}
}
},
leave ( node ) {
if ( node._scope ) {
replacementStack.pop();
names = replacementStack[ replacementStack.length - 1 ];
}
}
});
}

3
test/samples/import-default-as-other/_config.js

@ -0,0 +1,3 @@
module.exports = {
description: 'imports a default import by another name'
};

3
test/samples/import-default-as-other/foo.js

@ -0,0 +1,3 @@
export default function foo () {
return 42;
}

2
test/samples/import-default-as-other/main.js

@ -0,0 +1,2 @@
import bar from './foo';
assert.equal( bar(), 42 );

3
test/samples/import-named-function-as-other/_config.js

@ -0,0 +1,3 @@
module.exports = {
description: 'imports a named function by another name'
};

3
test/samples/import-named-function-as-other/foo.js

@ -0,0 +1,3 @@
export function foo () {
return 42;
}

2
test/samples/import-named-function-as-other/main.js

@ -0,0 +1,2 @@
import { foo as bar } from './foo';
assert.equal( bar(), 42 );
Loading…
Cancel
Save