From 9ae2d8c749576c6f65bb8ab3171180f3f9365235 Mon Sep 17 00:00:00 2001 From: Brian Donovan Date: Mon, 2 Jun 2014 10:28:56 -0700 Subject: [PATCH] Re-write the module transpiler with support for bindings. This commit removes support for AMD, YUI, and globals formats. These formats can be implemented as plugins. AMD should perhaps be included in core, but we'll see. --- .gitignore | 8 +- Gruntfile.js | 33 -- LICENSE | 2 +- bin/compile-modules | 84 ++++ lib/abstract_compiler.js | 118 ------ lib/amd_compiler.js | 106 ----- lib/cjs_compiler.js | 120 ------ lib/cli/convert.js | 146 +++++++ lib/compile-modules.js | 212 ---------- lib/compile_error.js | 4 - lib/compiler.js | 82 ---- lib/container.js | 150 +++++++ lib/declaration_info.js | 72 ++++ lib/exports.js | 319 ++++++++++++++ lib/file_resolver.js | 78 ++++ lib/formatters.js | 6 + lib/formatters/commonjs_formatter.js | 333 +++++++++++++++ lib/formatters/export_variable_formatter.js | 102 +++++ lib/formatters/module_variable_formatter.js | 97 +++++ lib/formatters/variable_formatter_base.js | 396 ++++++++++++++++++ lib/globals_compiler.js | 123 ------ lib/imports.js | 213 ++++++++++ lib/index.js | 18 +- lib/module.js | 193 +++++++++ lib/module_binding_declaration.js | 116 +++++ lib/module_binding_list.js | 233 +++++++++++ lib/module_binding_specifier.js | 120 ++++++ lib/parser.js | 96 ----- lib/replacement.js | 58 +++ lib/rewriter.js | 171 ++++++++ lib/sorting.js | 45 ++ lib/source_modifier.js | 50 --- lib/utils.js | 138 +++--- lib/writer.js | 64 +++ lib/yui_compiler.js | 81 ---- package.json | 42 +- tasks/es6ify.js | 21 - tasks/features.js | 121 ------ tasks/options/browserify.js | 12 - tasks/options/clean.js | 1 - tasks/options/concat.js | 20 - tasks/options/es6ify.js | 7 - tasks/options/features.js | 11 - tasks/options/jshint.js | 12 - tasks/options/simplemocha.js | 13 - tasks/options/transpile.js | 11 - tasks/options/uglify.js | 7 - test/examples/bare-import/exporter.js | 3 + test/examples/bare-import/importer.js | 5 + test/examples/bindings/exporter.js | 7 + test/examples/bindings/importer.js | 7 + test/examples/cycles-defaults/a.js | 5 + test/examples/cycles-defaults/b.js | 5 + test/examples/cycles-defaults/importer.js | 9 + test/examples/cycles/a.js | 9 + test/examples/cycles/b.js | 9 + test/examples/cycles/c.js | 9 + .../first.js | 4 + .../second.js | 15 + test/examples/export-default/exporter.js | 16 + test/examples/export-default/importer.js | 12 + test/examples/export-from/first.js | 3 + test/examples/export-from/second.js | 7 + test/examples/export-from/third.js | 4 + test/examples/export-function/exporter.js | 6 + test/examples/export-function/importer.js | 4 + test/examples/export-list/exporter.js | 10 + test/examples/export-list/importer.js | 9 + test/examples/export-var/exporter.js | 4 + test/examples/export-var/importer.js | 4 + test/examples/import-as/exporter.js | 5 + test/examples/import-as/importer.js | 7 + test/examples/import-chain/first.js | 3 + test/examples/import-chain/second.js | 4 + test/examples/import-chain/third.js | 4 + .../re-export-default-import/first.js | 5 + .../re-export-default-import/second.js | 4 + .../re-export-default-import/third.js | 4 + test/features/bare_import.amd.js | 7 - test/features/bare_import.cjs.js | 2 - test/features/bare_import.es6.js | 1 - test/features/bare_import.yui.js | 5 - test/features/block_comments.amd.js | 10 - test/features/block_comments.cjs.js | 6 - test/features/block_comments.es6.js | 7 - test/features/block_comments.globals.js | 8 - test/features/block_comments.yui.js | 9 - test/features/compatfix_option.amd.js | 6 - test/features/compatfix_option.cjs.js | 2 - test/features/compatfix_option.es6.js | 3 - test/features/compatfix_option.yui.js | 5 - test/features/export_default.amd.js | 8 - test/features/export_default.cjs.js | 4 - test/features/export_default.es6.js | 3 - test/features/export_default.globals.js | 6 - test/features/export_default.yui.js | 7 - test/features/export_from_module.amd.js | 7 - test/features/export_from_module.cjs.js | 3 - test/features/export_from_module.es6.js | 3 - test/features/export_from_module.globals.js | 5 - test/features/export_from_module.yui.js | 6 - test/features/export_function.amd.js | 7 - test/features/export_function.cjs.js | 3 - test/features/export_function.es6.js | 1 - test/features/export_function.globals.js | 5 - test/features/export_function.yui.js | 6 - test/features/export_identifier.amd.js | 8 - test/features/export_identifier.cjs.js | 4 - test/features/export_identifier.es6.js | 3 - test/features/export_identifier.globals.js | 6 - test/features/export_identifier.yui.js | 7 - test/features/export_specifier_set.amd.js | 10 - test/features/export_specifier_set.cjs.js | 6 - test/features/export_specifier_set.es6.js | 6 - test/features/export_specifier_set.globals.js | 8 - test/features/export_specifier_set.yui.js | 9 - test/features/export_var.amd.js | 7 - test/features/export_var.cjs.js | 3 - test/features/export_var.es6.js | 1 - test/features/export_var.globals.js | 5 - test/features/export_var.yui.js | 6 - test/features/import_default.amd.js | 6 - test/features/import_default.cjs.js | 2 - test/features/import_default.es6.js | 3 - test/features/import_default.globals.js | 4 - test/features/import_default.yui.js | 5 - test/features/import_default_as.amd.js | 6 - test/features/import_default_as.es6.js | 1 - test/features/import_default_as.yui.js | 5 - test/features/import_module.amd.js | 7 - test/features/import_module.cjs.js | 26 -- test/features/import_module.es6.js | 2 - test/features/import_module.yui.js | 6 - test/features/import_specifier.amd.js | 6 - test/features/import_specifier.cjs.js | 2 - test/features/import_specifier.es6.js | 1 - test/features/import_specifier.yui.js | 5 - test/features/import_specifier_set.amd.js | 8 - test/features/import_specifier_set.cjs.js | 4 - test/features/import_specifier_set.es6.js | 4 - test/features/import_specifier_set.globals.js | 6 - test/features/import_specifier_set.yui.js | 7 - test/features/multi_line_declaration.amd.js | 8 - test/features/multi_line_declaration.cjs.js | 4 - test/features/multi_line_declaration.es6.js | 6 - test/features/multi_line_declaration.yui.js | 7 - .../multiple_import_from_same_path.amd.js | 7 - .../multiple_import_from_same_path.cjs.js | 3 - .../multiple_import_from_same_path.es6.js | 4 - .../multiple_import_from_same_path.globals.js | 5 - .../multiple_import_from_same_path.yui.js | 6 - test/features/new_default.amd.js | 13 - test/features/new_default.cjs.js | 32 -- test/features/new_default.es6.js | 8 - test/features/new_default.yui.js | 12 - test/features/semicolons_optional.cjs.js | 2 - test/features/semicolons_optional.es6.js | 2 - test/features/semicolons_optional.yui.js | 5 - test/features/test_template.js.tmpl | 30 -- test/runner.js | 218 ++++++++++ 160 files changed, 3504 insertions(+), 1885 deletions(-) delete mode 100644 Gruntfile.js create mode 100755 bin/compile-modules delete mode 100644 lib/abstract_compiler.js delete mode 100644 lib/amd_compiler.js delete mode 100644 lib/cjs_compiler.js create mode 100644 lib/cli/convert.js delete mode 100644 lib/compile-modules.js delete mode 100644 lib/compile_error.js delete mode 100644 lib/compiler.js create mode 100644 lib/container.js create mode 100644 lib/declaration_info.js create mode 100644 lib/exports.js create mode 100644 lib/file_resolver.js create mode 100644 lib/formatters.js create mode 100644 lib/formatters/commonjs_formatter.js create mode 100644 lib/formatters/export_variable_formatter.js create mode 100644 lib/formatters/module_variable_formatter.js create mode 100644 lib/formatters/variable_formatter_base.js delete mode 100644 lib/globals_compiler.js create mode 100644 lib/imports.js create mode 100644 lib/module.js create mode 100644 lib/module_binding_declaration.js create mode 100644 lib/module_binding_list.js create mode 100644 lib/module_binding_specifier.js delete mode 100644 lib/parser.js create mode 100644 lib/replacement.js create mode 100644 lib/rewriter.js create mode 100644 lib/sorting.js delete mode 100644 lib/source_modifier.js create mode 100644 lib/writer.js delete mode 100644 lib/yui_compiler.js delete mode 100644 tasks/es6ify.js delete mode 100644 tasks/features.js delete mode 100644 tasks/options/browserify.js delete mode 100644 tasks/options/clean.js delete mode 100644 tasks/options/concat.js delete mode 100644 tasks/options/es6ify.js delete mode 100644 tasks/options/features.js delete mode 100644 tasks/options/jshint.js delete mode 100644 tasks/options/simplemocha.js delete mode 100644 tasks/options/transpile.js delete mode 100644 tasks/options/uglify.js create mode 100644 test/examples/bare-import/exporter.js create mode 100644 test/examples/bare-import/importer.js create mode 100644 test/examples/bindings/exporter.js create mode 100644 test/examples/bindings/importer.js create mode 100644 test/examples/cycles-defaults/a.js create mode 100644 test/examples/cycles-defaults/b.js create mode 100644 test/examples/cycles-defaults/importer.js create mode 100644 test/examples/cycles/a.js create mode 100644 test/examples/cycles/b.js create mode 100644 test/examples/cycles/c.js create mode 100644 test/examples/export-and-import-reference-share-var/first.js create mode 100644 test/examples/export-and-import-reference-share-var/second.js create mode 100644 test/examples/export-default/exporter.js create mode 100644 test/examples/export-default/importer.js create mode 100644 test/examples/export-from/first.js create mode 100644 test/examples/export-from/second.js create mode 100644 test/examples/export-from/third.js create mode 100644 test/examples/export-function/exporter.js create mode 100644 test/examples/export-function/importer.js create mode 100644 test/examples/export-list/exporter.js create mode 100644 test/examples/export-list/importer.js create mode 100644 test/examples/export-var/exporter.js create mode 100644 test/examples/export-var/importer.js create mode 100644 test/examples/import-as/exporter.js create mode 100644 test/examples/import-as/importer.js create mode 100644 test/examples/import-chain/first.js create mode 100644 test/examples/import-chain/second.js create mode 100644 test/examples/import-chain/third.js create mode 100644 test/examples/re-export-default-import/first.js create mode 100644 test/examples/re-export-default-import/second.js create mode 100644 test/examples/re-export-default-import/third.js delete mode 100644 test/features/bare_import.amd.js delete mode 100644 test/features/bare_import.cjs.js delete mode 100644 test/features/bare_import.es6.js delete mode 100644 test/features/bare_import.yui.js delete mode 100644 test/features/block_comments.amd.js delete mode 100644 test/features/block_comments.cjs.js delete mode 100644 test/features/block_comments.es6.js delete mode 100644 test/features/block_comments.globals.js delete mode 100644 test/features/block_comments.yui.js delete mode 100644 test/features/compatfix_option.amd.js delete mode 100644 test/features/compatfix_option.cjs.js delete mode 100644 test/features/compatfix_option.es6.js delete mode 100644 test/features/compatfix_option.yui.js delete mode 100644 test/features/export_default.amd.js delete mode 100644 test/features/export_default.cjs.js delete mode 100644 test/features/export_default.es6.js delete mode 100644 test/features/export_default.globals.js delete mode 100644 test/features/export_default.yui.js delete mode 100644 test/features/export_from_module.amd.js delete mode 100644 test/features/export_from_module.cjs.js delete mode 100644 test/features/export_from_module.es6.js delete mode 100644 test/features/export_from_module.globals.js delete mode 100644 test/features/export_from_module.yui.js delete mode 100644 test/features/export_function.amd.js delete mode 100644 test/features/export_function.cjs.js delete mode 100644 test/features/export_function.es6.js delete mode 100644 test/features/export_function.globals.js delete mode 100644 test/features/export_function.yui.js delete mode 100644 test/features/export_identifier.amd.js delete mode 100644 test/features/export_identifier.cjs.js delete mode 100644 test/features/export_identifier.es6.js delete mode 100644 test/features/export_identifier.globals.js delete mode 100644 test/features/export_identifier.yui.js delete mode 100644 test/features/export_specifier_set.amd.js delete mode 100644 test/features/export_specifier_set.cjs.js delete mode 100644 test/features/export_specifier_set.es6.js delete mode 100644 test/features/export_specifier_set.globals.js delete mode 100644 test/features/export_specifier_set.yui.js delete mode 100644 test/features/export_var.amd.js delete mode 100644 test/features/export_var.cjs.js delete mode 100644 test/features/export_var.es6.js delete mode 100644 test/features/export_var.globals.js delete mode 100644 test/features/export_var.yui.js delete mode 100644 test/features/import_default.amd.js delete mode 100644 test/features/import_default.cjs.js delete mode 100644 test/features/import_default.es6.js delete mode 100644 test/features/import_default.globals.js delete mode 100644 test/features/import_default.yui.js delete mode 100644 test/features/import_default_as.amd.js delete mode 100644 test/features/import_default_as.es6.js delete mode 100644 test/features/import_default_as.yui.js delete mode 100644 test/features/import_module.amd.js delete mode 100644 test/features/import_module.cjs.js delete mode 100644 test/features/import_module.es6.js delete mode 100644 test/features/import_module.yui.js delete mode 100644 test/features/import_specifier.amd.js delete mode 100644 test/features/import_specifier.cjs.js delete mode 100644 test/features/import_specifier.es6.js delete mode 100644 test/features/import_specifier.yui.js delete mode 100644 test/features/import_specifier_set.amd.js delete mode 100644 test/features/import_specifier_set.cjs.js delete mode 100644 test/features/import_specifier_set.es6.js delete mode 100644 test/features/import_specifier_set.globals.js delete mode 100644 test/features/import_specifier_set.yui.js delete mode 100644 test/features/multi_line_declaration.amd.js delete mode 100644 test/features/multi_line_declaration.cjs.js delete mode 100644 test/features/multi_line_declaration.es6.js delete mode 100644 test/features/multi_line_declaration.yui.js delete mode 100644 test/features/multiple_import_from_same_path.amd.js delete mode 100644 test/features/multiple_import_from_same_path.cjs.js delete mode 100644 test/features/multiple_import_from_same_path.es6.js delete mode 100644 test/features/multiple_import_from_same_path.globals.js delete mode 100644 test/features/multiple_import_from_same_path.yui.js delete mode 100644 test/features/new_default.amd.js delete mode 100644 test/features/new_default.cjs.js delete mode 100644 test/features/new_default.es6.js delete mode 100644 test/features/new_default.yui.js delete mode 100644 test/features/semicolons_optional.cjs.js delete mode 100644 test/features/semicolons_optional.es6.js delete mode 100644 test/features/semicolons_optional.yui.js delete mode 100644 test/features/test_template.js.tmpl create mode 100644 test/runner.js diff --git a/.gitignore b/.gitignore index df99ae0..32efc03 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,2 @@ -/node_modules/ -tmp/ -test/index.html -test/.generated -bin/ -dist/ +node_modules/ +test/results diff --git a/Gruntfile.js b/Gruntfile.js deleted file mode 100644 index 6e08d11..0000000 --- a/Gruntfile.js +++ /dev/null @@ -1,33 +0,0 @@ -function config(name) { - return require('./tasks/options/' + name); -} - -module.exports = function(grunt) { - var path = require('path'); - - // Load node modules providing grunt tasks. - require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks); - - grunt.initConfig({ - clean : config('clean'), - transpile : config('transpile'), - browserify : config('browserify'), - es6ify : config('es6ify'), - concat : config('concat'), - uglify : config('uglify'), - jshint : config('jshint'), - - simplemocha : config('simplemocha'), - features : config('features') - }); - - // Load local tasks. - grunt.task.loadTasks('./tasks'); - - grunt.registerTask('build', - ['clean', 'transpile', 'es6ify', 'browserify', 'concat', 'uglify']); - - grunt.registerTask('test', ['features', 'simplemocha']); - - grunt.registerTask('default', ['test']); -}; diff --git a/LICENSE b/LICENSE index c38a291..cabe814 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright 2013 Square Inc. +Copyright 2014 Square Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/bin/compile-modules b/bin/compile-modules new file mode 100755 index 0000000..04c9c89 --- /dev/null +++ b/bin/compile-modules @@ -0,0 +1,84 @@ +#!/usr/bin/env node + +var Path = require('path'); +var exe = Path.basename(process.argv[1]); +var getopt = require('posix-getopt'); +var parser = new getopt.BasicParser('h(help)v(version)', process.argv); +var option; + +function usage(puts) { + puts(exe + ' [--help] [--version] []'); + puts(); + puts('Commands'); + puts(); + puts(' convert Converts modules from `import`/`export` to an ES5 equivalent.'); + puts(' help Display help for a given command.'); +} + +function makeWriteLine(stream) { + return function(line) { + if (!line || line[line.length - 1] !== '\n') { + line = (line || '') + '\n'; + } + stream.write(line); + }; +} + +var puts = makeWriteLine(process.stdout); +var eputs = makeWriteLine(process.stderr); + +while ((option = parser.getopt()) !== undefined) { + if (option.error) { + usage(); + process.exit(1); + } + + switch (option.option) { + case 'h': + usage(puts); + process.exit(0); + break; + + case 'v': + puts(exe + ' v' + require(Path.join(__dirname, '../package.json')).version); + process.exit(0); + break; + } +} + +var args = process.argv; +var offset = parser.optind(); + +var commandName = args[offset]; +if (commandName === 'help') { + commandName = args[offset + 1]; + args = ['--help'].concat(args.slice(offset + 2 /* skip 'help' and command name */)); +} else { + args = args.slice(offset + 1); +} + +if (typeof commandName !== 'string') { + usage(puts); + process.exit(1); +} + +var command; + +try { + command = require(Path.join('../lib/cli', commandName)); +} catch (ex) { + usage(eputs); + process.exit(1); +} + +try { + var exitCode = command.run(args, puts, eputs); + process.exit(exitCode); +} catch (ex) { + if (ex.constructor.name === 'AssertionError') { + eputs('error: ' + exe + ' ' + commandName + ' -- ' + ex.message); + process.exit(1); + } else { + throw ex; + } +} diff --git a/lib/abstract_compiler.js b/lib/abstract_compiler.js deleted file mode 100644 index b2d4525..0000000 --- a/lib/abstract_compiler.js +++ /dev/null @@ -1,118 +0,0 @@ -import CompileError from './compile_error'; -import { isEmpty, array, forEach } from './utils'; - -class AbstractCompiler { - constructor(compiler, options) { - this.compiler = compiler; - - this.exports = compiler.exports; - this.exportDefault = compiler.exportDefault; - this.imports = compiler.imports; - this.directives = compiler.directives; - - this.moduleName = compiler.moduleName; - this.lines = compiler.lines; - this.string = compiler.string; - - this.options = options; - - var allDependencies = this.imports.concat(this.exports.filter(function(export_) { - return export_.source !== null; - })); - - this.dependencyNames = array.uniq(allDependencies.map(function(dep) { - return dep.source.value; - })); - } - - buildImports() { - var imports = this.imports, - moduleImports = this.moduleImports, - source = this.source; - - for (var idx = 0; idx < imports.length; idx++) { - var import_ = imports[idx], - replacement = ""; - - var dependencyName = import_.source.value; - - if (import_.type === "ModuleDeclaration" && import_.source.type === "Literal") { - replacement = this.doModuleImport(import_.id.name, dependencyName, idx); - } else if (import_.type === "ImportDeclaration") { - if (import_.kind === "default") { - // var name = __dependencyX__; - var specifier = import_.specifiers[0]; - replacement = this.doDefaultImport(specifier.id.name, dependencyName, idx); - } else if (import_.kind === "named") { - // var one = __dependencyX__.one; - // var two = __dependencyX__.two; - replacement = this.doImportSpecifiers(import_, idx); - } else if (import_.kind === undefined) { - replacement = this.doBareImport(import_.source.value); - } - } - source.replace(import_.range[0], import_.range[1], replacement); - } - } - - buildExports() { - var source = this.source, - exports_ = this.exports; - - for (var export_ of exports_) { - var replacement = ""; - - if (export_.default) { - var identifier = export_.declaration.name || null; - source.replace(export_.range[0], - export_.declaration.range[0] - 1, - this.doDefaultExport(identifier)); - } else if (export_.specifiers) { - var reexport; - if (export_.source) { - reexport = export_.source.value; - } - for (var specifier of export_.specifiers) { - replacement += this.doExportSpecifier(specifier.id.name, reexport); - } - source.replace(export_.range[0], export_.range[1], replacement); - } else if (export_.declaration) { - - if (export_.declaration.type === "VariableDeclaration") { - var name = export_.declaration.declarations[0].id.name; - - // remove the "export" keyword - source.replace(export_.range[0], export_.declaration.range[0] -1, ""); - // add a new line - replacement = this.doExportDeclaration(name); - source.replace(export_.range[1], export_.range[1], replacement); - } else if (export_.declaration.type === "FunctionDeclaration") { - var name = export_.declaration.id.name; - - source.replace(export_.range[0], export_.declaration.range[0] - 1, ""); - - replacement = this.doExportDeclaration(name); - source.replace(export_.range[1] + 1, export_.range[1] + 1, replacement); - } else if (export_.declaration.type === "Identifier") { - var name = export_.declaration.name; - - replacement = this.doExportDeclaration(name); - source.replace(export_.range[0], export_.range[1] - 1, replacement); - } - - } - } - } - - indentLines(indent = " ") { - var innerLines = this.source.toString().split("\n"); - var inner = innerLines.reduce(function(acc, item) { - if (item === "") return acc + "\n"; - return acc + indent + item + "\n"; - }, ""); - - return inner.replace(/\s+$/, ""); - } -} - -export default AbstractCompiler; diff --git a/lib/amd_compiler.js b/lib/amd_compiler.js deleted file mode 100644 index 0d9e633..0000000 --- a/lib/amd_compiler.js +++ /dev/null @@ -1,106 +0,0 @@ -import AbstractCompiler from './abstract_compiler'; -import SourceModifier from './source_modifier'; - -class AMDCompiler extends AbstractCompiler { - stringify() { - var string = this.string.toString(); // string is actually a node buffer - this.source = new SourceModifier(string); - - this.map = []; - var out = this.buildPreamble(this.exports.length > 0); - - // build* mutates this.source - this.buildImports(); - this.buildExports(); - - out += this.indentLines(" "); - out += "\n });"; - - return out; - } - - buildPreamble(hasExports) { - var out = "", - dependencyNames = this.dependencyNames; - - if (hasExports) dependencyNames.push("exports"); - - out += "define("; - if (this.moduleName) out += `"${this.moduleName}",`; - out += "\n ["; - - // build preamble - var idx; - for (idx = 0; idx < dependencyNames.length; idx++) { - var name = dependencyNames[idx]; - out += `"${name}"`; - if (!(idx === dependencyNames.length - 1)) out += ","; - } - - out += "],\n function("; - - for (idx = 0; idx < dependencyNames.length; idx++) { - if (dependencyNames[idx] === "exports") { - out += "__exports__"; - } else { - out += `__dependency${idx+1}__`; - this.map[dependencyNames[idx]] = idx+1; - } - if (!(idx === dependencyNames.length - 1)) out += ", "; - } - - out += ") {\n"; - - out += ' "use strict";\n'; - - return out; - } - - doModuleImport(name, dependencyName, idx) { - return `var ${name} = __dependency${this.map[dependencyName]}__;\n`; - } - - doBareImport(name) { - return ""; - } - - doDefaultImport(name, dependencyName, idx) { - if (this.options.compatFix === true) { - return `var ${name} = __dependency${this.map[dependencyName]}__["default"] || __dependency${this.map[dependencyName]}__;\n`; - } else { - return `var ${name} = __dependency${this.map[dependencyName]}__["default"];\n`; - } - } - - doNamedImport(name, dependencyName, alias) { - return `var ${alias} = __dependency${this.map[dependencyName]}__.${name};\n`; - } - - doExportSpecifier(name, reexport) { - if (reexport) { - return `__exports__.${name} = __dependency${this.map[reexport]}__.${name};\n`; - } - return `__exports__.${name} = ${name};\n`; - } - - doExportDeclaration(name) { - return `\n__exports__.${name} = ${name};`; - } - - doDefaultExport() { - return `__exports__["default"] = `; - } - - doImportSpecifiers(import_, idx) { - var dependencyName = import_.source.value; - var replacement = ""; - for (var specifier of import_.specifiers) { - var alias = specifier.name ? specifier.name.name : specifier.id.name; - replacement += this.doNamedImport(specifier.id.name, dependencyName, alias); - } - return replacement; - } - -} - -export default AMDCompiler; diff --git a/lib/cjs_compiler.js b/lib/cjs_compiler.js deleted file mode 100644 index a81875f..0000000 --- a/lib/cjs_compiler.js +++ /dev/null @@ -1,120 +0,0 @@ -import AbstractCompiler from './abstract_compiler'; -import SourceModifier from './source_modifier'; -import { string } from './utils'; - -const SAFE_WARN_NAME = "__es6_transpiler_warn__"; -const SAFE_WARN_SOURCE = string.ltrim(string.unindent(` - function ${SAFE_WARN_NAME}(warning) { - if (typeof console === 'undefined') { - } else if (typeof console.warn === "function") { - console.warn(warning); - } else if (typeof console.log === "function") { - console.log(warning); - } - }`)); - -const MODULE_OBJECT_BUILDER_NAME = "__es6_transpiler_build_module_object__"; -const MODULE_OBJECT_BUILDER_SOURCE = string.ltrim(string.unindent(` - function ${MODULE_OBJECT_BUILDER_NAME}(name, imported) { - var moduleInstanceObject = Object.create ? Object.create(null) : {}; - if (typeof imported === "function") { - ${SAFE_WARN_NAME}("imported module '"+name+"' exported a function - this may not work as expected"); - } - for (var key in imported) { - if (Object.prototype.hasOwnProperty.call(imported, key)) { - moduleInstanceObject[key] = imported[key]; - } - } - if (Object.freeze) { - Object.freeze(moduleInstanceObject); - } - return moduleInstanceObject; - }`)); - -class CJSCompiler extends AbstractCompiler { - stringify() { - var string = this.string.toString(); // string is actually a node buffer - this.source = new SourceModifier(string); - this.prelude = []; - - this.buildImports(); - this.buildExports(); - - var out = `"use strict";\n`; - for (var source of this.prelude) { - out += source + "\n"; - } - out += this.source.toString(); - out = out.trim(); - return out; - } - - doModuleImport(name, dependencyName, idx) { - this.ensureHasModuleObjectBuilder(); - // NOTE: Don't be tempted to move `require("${dependencyName}")` into the builder. - // This require call is here so that browserify and the like will be able - // to statically analyze the file's requirements. - return `var ${name} = ${MODULE_OBJECT_BUILDER_NAME}("${name}", require("${dependencyName}"));\n`; - } - - ensureHasModuleObjectBuilder() { - this.ensureHasSafeWarn(); - this.ensureInPrelude(MODULE_OBJECT_BUILDER_NAME, MODULE_OBJECT_BUILDER_SOURCE); - } - - ensureHasSafeWarn() { - this.ensureInPrelude(SAFE_WARN_NAME, SAFE_WARN_SOURCE); - } - - ensureInPrelude(name, source) { - if (!this.prelude[name]) { - this.prelude[name] = true; - this.prelude.push(source); - } - } - - doBareImport(name) { - return `require("${name}");`; - } - - doDefaultImport(name, dependencyName, idx) { - if (this.options.compatFix === true) { - return `var ${name} = require("${dependencyName}")["default"] || require("${dependencyName}");\n`; - } else { - return `var ${name} = require("${dependencyName}")["default"];\n`; - } - } - - doNamedImport(name, dependencyName, alias) { - return `var ${alias} = require("${dependencyName}").${name};\n`; - } - - doExportSpecifier(name, reexport) { - if (reexport) { - return `exports.${name} = require("${reexport}").${name};\n`; - } - return `exports.${name} = ${name};\n`; - } - - doExportDeclaration(name) { - return `\nexports.${name} = ${name};`; - } - - doDefaultExport() { - return `exports["default"] = `; - } - - doImportSpecifiers(import_, idx) { - var dependencyName = import_.source.value; - var replacement = ""; - - for (var specifier of import_.specifiers) { - var alias = specifier.name ? specifier.name.name : specifier.id.name; - replacement += this.doNamedImport(specifier.id.name, dependencyName, alias); - } - return replacement; - } - -} - -export default CJSCompiler; diff --git a/lib/cli/convert.js b/lib/cli/convert.js new file mode 100644 index 0000000..a5a6083 --- /dev/null +++ b/lib/cli/convert.js @@ -0,0 +1,146 @@ +/* jshint node:true, undef:true, unused:true */ + +var assert = require('assert'); +var Path = require('path'); +var recast = require('recast'); + +var formatters = require('../formatters'); +var FileResolver = require('../file_resolver'); +var Container = require('../container'); +var FileResolver = require('../file_resolver'); + +var getopt = require('posix-getopt'); +var exe = Path.basename(process.argv[1]); + +exports.run = function(args, puts, eputs) { + var offset = 0; + + var files = []; + var includePaths = [process.cwd()]; + var output; + var formatter = formatters[formatters.DEFAULT]; + var resolverClasses = [FileResolver]; + + while (offset < args.length) { + var parser = new getopt.BasicParser('h(help)o:(output)I:(include)f:(format)r:(resolver)', ['', ''].concat(args.slice(offset))); + var option; + + while ((option = parser.getopt()) !== undefined) { + if (option.error) { + usage(eputs); + return 1; + } + + switch (option.option) { + case 'h': + usage(puts); + return 0; + + case 'o': + output = option.optarg; + break; + + case 'I': + includePaths.push(option.optarg); + break; + + case 'f': + formatter = formatters[option.optarg]; + if (!formatter) { + try { formatter = require(option.optarg); } + catch (ex) {} + } + if (!formatter) { + usage(eputs); + return 1; + } + break; + + case 'r': + try { + resolverClasses.push(require(option.optarg)); + } + catch (ex) { + usage(eputs); + return 1; + } + break; + } + } + + for (offset += parser.optind() - 2; args[offset] && args[offset][0] !== '-'; offset++) { + files.push(args[offset]); + } + } + + assert.ok( + files.length > 0, + 'Please provide at least one file to convert.' + ); + + if (typeof formatter === 'function') { + formatter = new formatter(); + } + + var resolvers = resolverClasses.map(function(resolverClass) { + return new resolverClass(includePaths); + }); + var container = new Container({ + formatter: formatter, + resolvers: resolvers + }); + + files.forEach(function(file) { + container.getModule(file); + }); + + if (output) { + container.write(output); + } else { + var outputs = container.convert(); + assert.equal( + outputs.length, 1, + 'Cannot output ' + outputs.length + ' files to stdout. ' + + 'Please use the --output flag to specify where to put the ' + + 'files or choose a formatter that concatenates.' + ); + process.stdout.write(recast.print(outputs[0]).code); + } + +}; + +function bold(string) { + return '\x1b[01m' + string + '\x1b[0m'; +} + +function usage(puts) { + puts(exe + ' convert [-I ] [-o ] [-f ] [-r ] [ ...]'); + puts(); + puts(bold('Description')); + puts(); + puts(' Converts the given modules by changing `import`/`export` statements to an ES5 equivalent.'); + puts(); + puts(bold('Options')); + puts(); + puts(' -I, --include Check the given path for imported modules (usable multiple times).'); + puts(' -o, --output File or directory to output converted files.'); + puts(' -f, --format Path to custom formatter or choose from built-in formats.'); + puts(' -r, --resolver Path to custom resolver (usable multiple times).'); + puts(' -h, --help Show this help message.'); + puts(); + puts(bold('Formats')); + puts(); + puts(' commonjs - convert modules to files using CommonJS `require` and `exports` objects.'); + puts(' module-variable - concatenate modules optimizing for runtime performance.'); + puts(' export-variable - concatenate modules optimizing for compressibility (enables better tree-shaking).'); + puts(); + puts(' You may provide custom a formatter by passing the path to your module to the `--format` option. See the'); + puts(' source of any of the built-in formatters for details on how to build your own.'); + puts(); + puts(bold('Resolvers')); + puts(); + puts(' Resolvers resolve import paths to modules. The default resolver will search the include paths provided'); + puts(' by `--include` arguments and the current working directory. To provide custom resolver logic, pass the'); + puts(' path to your resolver module providing a `resolveModule` function or class with an instance method with'); + puts(' this signature: `resolveModule(importedPath:String, fromModule:?Module, container:Container): Module`.'); +} diff --git a/lib/compile-modules.js b/lib/compile-modules.js deleted file mode 100644 index 058d08e..0000000 --- a/lib/compile-modules.js +++ /dev/null @@ -1,212 +0,0 @@ -import optimist from 'optimist'; -import fs from 'fs'; -import path from 'path'; -import through from 'through'; - -function extend(target, ...sources) { - var toString = {}.toString; - - sources.forEach(function(source) { - for (var key in source) { - target[key] = source[key]; - } - }); - - return target; -} - -class CLI { - constructor(Compiler, stdin=process.stdin, stdout=process.stdout, fs_=fs) { - this.Compiler = Compiler; - this.stdin = stdin; - this.stdout = stdout; - this.fs = fs_; - } - - start(argv) { - var options = this.parseArgs(argv); - - if (options.help) { - this.argParser(argv).showHelp(); - } else if (options.stdio) { - this.processStdio(options); - } else { - for (var i = 2; i < options._.length; i++) { - var filename = options._[i]; - this.processPath(filename, options); - } - } - } - - parseArgs(argv) { - var args = this.argParser(argv).argv; - - if (args.imports) { - var imports = {}; - args.imports.split(',').forEach(function(pair) { - var [requirePath, global] = pair.split(':'); - imports[requirePath] = global; - }); - args.imports = imports; - } - - if (args.global) { - args.into = args.global; - } - - return args; - } - - argParser(argv) { - return optimist(argv).usage('compile-modules usage:\n\n Using files:\n compile-modules INPUT --to DIR [--infer-name] [--type TYPE] [--imports PATH:GLOBAL]\n\n Using stdio:\n compile-modules --stdio [--type TYPE] [--imports PATH:GLOBAL] [--module-name MOD]').options({ - type: { - "default": 'amd', - describe: 'The type of output (one of "amd", "yui", "cjs", or "globals")' - }, - to: { - describe: 'A directory in which to write the resulting files' - }, - imports: { - describe: 'A list of path:global pairs, comma separated (e.g. jquery:$,ember:Ember)' - }, - 'infer-name': { - "default": false, - type: 'boolean', - describe: 'Automatically generate names for AMD and YUI modules' - }, - 'module-name': { - describe: 'The name of the outputted module', - alias: 'm' - }, - stdio: { - "default": false, - type: 'boolean', - alias: 's', - describe: 'Use stdin and stdout to process a file' - }, - global: { - describe: 'When the type is `globals`, the name of the global to export into' - }, - help: { - "default": false, - type: 'boolean', - alias: 'h', - describe: 'Shows this help message' - } - }).check(({type}) => type === 'amd' || type === 'yui' || type === 'cjs' || type === 'globals') - .check(args => !args['infer-name'] || !args.m) - .check(args => (args.stdio && args.type === 'amd') ? !args['infer-name'] : true) - .check(args => (args.stdio && args.type === 'yui') ? !args['infer-name'] : true) - .check(args => args.stdio || args.to || args.help) - .check(args => args.imports ? args.type === 'globals' : args.type !== 'globals'); - } - - processStdio(options) { - this.processIO(this.stdin, this.stdout, options); - } - - processIO(input, output, options) { - var data = '', - self = this; - - function write(chunk) { - data += chunk; - } - - function end() { - /* jshint -W040 */ - this.queue(self._compile(data, options.m, options.type, options)); - this.queue(null); - } - - input.pipe(through(write, end)).pipe(output); - } - - processPath(filename, options) { - this.fs.stat(filename, function(err, stat) { - if (err) { - throw new Error(err); - } else if (stat.isDirectory()) { - this.processDirectory(filename, options); - } else { - this.processFile(filename, options); - } - }.bind(this)); - } - - processDirectory(dirname, options) { - this.fs.readdir(dirname, function(err, children) { - if (err) { - console.error(err.message); - process.exit(1); - } - children.forEach(function(child) { - this.processPath(path.join(dirname, child), options); - }.bind(this)); - }.bind(this)); - } - - processFile(filename, options) { - function normalizePath(p) { - return p.replace(/\\/g, '/'); - } - - var ext = path.extname(filename), - basenameNoExt = path.basename(filename, ext), - dirname = path.dirname(filename), - pathNoExt = normalizePath(path.join(dirname, basenameNoExt)), - output, - outputFilename = normalizePath(path.join(options.to, filename)), - moduleName = options['infer-name'] ? pathNoExt : null; - - options = extend({}, options, {m: moduleName}); - this._mkdirp(path.dirname(outputFilename)); - - this.processIO( - this.fs.createReadStream(filename), - this.fs.createWriteStream(outputFilename), - options - ); - } - - _compile(input, moduleName, type, options) { - var compiler, method; - type = { - amd: 'AMD', - yui: 'YUI', - cjs: 'CJS', - globals: 'Globals' - }[type]; - compiler = new this.Compiler(input, moduleName, options); - method = "to" + type; - return compiler[method](); - } - - _mkdirp(directory) { - var prefix; - if (this.fs.existsSync(directory)) { - return; - } - prefix = path.dirname(directory); - if (prefix !== '.' && prefix !== '/') { - this._mkdirp(prefix); - } - return this.fs.mkdirSync(directory); - } -} - -CLI.start = function(Compiler, argv, stdin=process.stdin, stdout=process.stdout, fs_=fs) { - return new CLI(Compiler, stdin, stdout, fs_).start(argv); -}; - -function requireMain() { - var root = path.join(__dirname, '..'), - pkgPath = path.join(root, 'package.json'), - pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); - - return require(path.join(root, pkg.main)); -} - -let Compiler = requireMain().Compiler; - -CLI.start(Compiler, process.argv); diff --git a/lib/compile_error.js b/lib/compile_error.js deleted file mode 100644 index ad66130..0000000 --- a/lib/compile_error.js +++ /dev/null @@ -1,4 +0,0 @@ -class CompileError extends Error { -} - -export default CompileError; diff --git a/lib/compiler.js b/lib/compiler.js deleted file mode 100644 index e5847d9..0000000 --- a/lib/compiler.js +++ /dev/null @@ -1,82 +0,0 @@ -import AMDCompiler from './amd_compiler'; -import YUICompiler from './yui_compiler'; -import CJSCompiler from './cjs_compiler'; -import GlobalsCompiler from './globals_compiler'; -import { Unique } from './utils'; -import Parser from './parser'; - -/** - * Public interface to the transpiler. - * - * @class Compiler - * @constructor - * @param {String} string Input string. - * @param {String} moduleName The name of the module to output. - * @param {Object} options Configuration object. - */ -class Compiler { - constructor(string, moduleName, options) { - if (moduleName == null) { - moduleName = null; - } - - if (options == null) { - options = {}; - } - - this.string = string; - this.moduleName = moduleName; - this.options = options; - - this.inBlockComment = false; - this.reExportUnique = new Unique('reexport'); - - this.parse(); - } - - parse() { - var parser = new Parser(this.string); - this.imports = parser.imports; - this.exports = parser.exports; - this.exportDefault = parser.exportDefault; - this.directives = parser.directives; - } - - /** - * Transpiles an ES6 module to AMD. - * @method toAMD - * @return {String} The transpiled output - */ - toAMD() { - return new AMDCompiler(this, this.options).stringify(); - } - - /** - * Transpiles an ES6 module to YUI. - * @method toYUI - * @return {String} The transpiled output - */ - toYUI() { - return new YUICompiler(this, this.options).stringify(); - } - - /** - * Transpiles an ES6 module to CJS. - * @method toCJS - * @return {String} The transpiled output - */ - toCJS() { - return new CJSCompiler(this, this.options).stringify(); - } - - /** - * Transpiles an ES6 module to IIFE-wrapped globals. - * @method toGlobals - * @return {String} The transpiled output - */ - toGlobals() { - return new GlobalsCompiler(this, this.options).stringify(); - } -} - -export default Compiler; diff --git a/lib/container.js b/lib/container.js new file mode 100644 index 0000000..92aa494 --- /dev/null +++ b/lib/container.js @@ -0,0 +1,150 @@ +/* jshint node:true, undef:true, unused:true */ + +var assert = require('assert'); + +var Rewriter = require('./rewriter'); +var Writer = require('./writer'); + +/** + * Represents a container of modules for the given options. + * + * @constructor + * @param {{resolver: Resolver}} options + */ +function Container(options) { + var formatter = options && options.formatter; + if (typeof formatter === 'function') { + formatter = new formatter(); + } + + assert.ok( + formatter, + 'missing required option `formatter`' + ); + assert.equal( + typeof formatter.reference, 'function', + 'option `formatter` must have function `reference`' + ); + assert.equal( + typeof formatter.build, 'function', + 'option `formatter` must have function `build`' + ); + + var resolvers = options && options.resolvers; + + assert.ok( + resolvers && resolvers.length > 0, + 'at least one resolver is required' + ); + resolvers.forEach(function(resolver) { + assert.equal( + typeof resolver.resolveModule, 'function', + '`resolver` must have `resolveModule` function: ' + resolver + ); + }); + + Object.defineProperties(this, { + modules: { + value: Object.create(null), + enumerable: false + }, + + formatter: { + value: formatter, + enumerable: false + }, + + resolvers: { + value: resolvers, + enumerable: false + }, + + options: { + value: options, + enumerable: false + } + }); +} + +/** + * Gets a module by resolving `path`. If `path` is resolved to the same path + * as a previous call, the same object will be returned. + * + * @param {string} importedPath + * @param {?Module} fromModule + * @return {Module} + */ +Container.prototype.getModule = function(importedPath, fromModule) { + for (var i = 0, length = this.resolvers.length; i < length; i++) { + var resolvedModule = this.resolvers[i].resolveModule( + importedPath, + fromModule, + this + ); + + if (resolvedModule) { + this.modules[resolvedModule.path] = resolvedModule; + return resolvedModule; + } + } + + throw new Error( + 'missing module import' + + (fromModule ? 'from ' + fromModule.relativePath : '') + + ' for path: ' + importedPath + ); +}; + +/** + * Get a cached module by a resolved path. + * + * @param {string} resolvedPath + * @return {?Module} + */ +Container.prototype.getCachedModule = function(resolvedPath) { + return this.modules[resolvedPath]; +}; + +Container.prototype.write = function(target) { + var files = this.convert(); + var writer = new Writer(target); + writer.write(files); +}; + +Container.prototype.convert = function() { + if (this.formatter.beforeConvert) { + this.formatter.beforeConvert(this); + } + + var modules = this.getModules(); + + var rewriter = new Rewriter(this.formatter); + rewriter.rewrite(modules); + + var formatter = this.formatter; + return formatter.build(modules); +}; + +Container.prototype.findImportedModules = function() { + var knownModules; + var lastModuleCount = 0; + + while ((knownModules = this.getModules()).length !== lastModuleCount) { + lastModuleCount = knownModules.length; + for (var i = 0; i < lastModuleCount; i++) { + // Force loading of imported modules. + /* jshint expr:true */ + knownModules[i].imports.modules; + /* jshint expr:false */ + } + } +}; + +Container.prototype.getModules = function() { + var modules = this.modules; + return Object.keys(modules).map(function(key) { + return modules[key]; + }); +}; + +module.exports = Container; diff --git a/lib/declaration_info.js b/lib/declaration_info.js new file mode 100644 index 0000000..c0e897d --- /dev/null +++ b/lib/declaration_info.js @@ -0,0 +1,72 @@ +/* jshint node:true, undef:true, unused:true */ + +var recast = require('recast'); +var types = recast.types; +var n = types.namedTypes; + +/** + * Represents information about a declaration that creates a local binding + * represented by `identifier`. For example, given that `declaration` is the + * following variable declaration: + * + * var a = 1; + * + * Then `identifier` references the `a` node in the variable declaration's + * first declarator. Likewise, given that `declaration` is this function + * declaration: + * + * function add(a, b) {} + * + * Then `identifier` references the `add` node, the declaration's `id`. + * + * @constructor + * @param {ast-types.Node} declaration + * @param {ast-types.Identifier} identifier + */ +function DeclarationInfo(declaration, identifier) { + /** + * @type {ast-types.Node} + * @property declaration + */ + this.declaration = declaration; + /** + * @type {ast-types.Identifier} + * @property identifier + */ + this.identifier = identifier; +} + +/** + * Get the declaration info for the given identifier path, if the identifier is + * actually part of a declaration. + * + * @param {ast-types.NodePath} identifierPath + * @return {?DeclarationInfo} + */ +DeclarationInfo.forIdentifierPath = function(identifierPath) { + if (n.VariableDeclarator.check(identifierPath.parent.node)) { + return new DeclarationInfo( + identifierPath.parent.parent.node, + identifierPath.node + ); + } else if (n.ClassDeclaration.check(identifierPath.parent.node)) { + return new DeclarationInfo( + identifierPath.parent.node, + identifierPath.node + ); + } else if (n.FunctionDeclaration.check(identifierPath.parent.node)) { + return new DeclarationInfo( + identifierPath.parent.node, + identifierPath.node + ); + } else if (n.ImportSpecifier.check(identifierPath.parent.node)) { + return new DeclarationInfo( + identifierPath.parent.parent.node, + identifierPath.node + ); + } else { + return null; + } +}; + +module.exports = DeclarationInfo; diff --git a/lib/exports.js b/lib/exports.js new file mode 100644 index 0000000..91438e9 --- /dev/null +++ b/lib/exports.js @@ -0,0 +1,319 @@ +/* jshint node:true, undef:true, unused:true */ + +var assert = require('assert'); + +var recast = require('recast'); +var types = recast.types; +var n = types.namedTypes; + +var ModuleBindingList = require('./module_binding_list'); +var ModuleBindingDeclaration = require('./module_binding_declaration'); +var ModuleBindingSpecifier = require('./module_binding_specifier'); +var DeclarationInfo = require('./declaration_info'); + +var utils = require('./utils'); +var memo = utils.memo; +var extend = utils.extend; +var sourcePosition = utils.sourcePosition; + +/** + * Represents a list of the exports for the given module. + * + * @constructor + * @param {Module} mod + */ +function ExportDeclarationList(mod) { + ModuleBindingList.call(this, mod); +} +extend(ExportDeclarationList, ModuleBindingList); + +/** + * @private + * @param {ast-types.Node} node + * @return {boolean} + */ +ExportDeclarationList.prototype.isMatchingBinding = function(node) { + return n.ExportDeclaration.check(node); +}; + +/** + * Gets an export declaration for the given `node`. + * + * @private + * @param {ast-types.ExportDeclaration} node + * @return {Import} + */ +ExportDeclarationList.prototype.declarationForNode = function(node) { + if (node.default) { + return new DefaultExportDeclaration(this.module, node); + } else if (n.VariableDeclaration.check(node.declaration)) { + return new VariableExportDeclaration(this.module, node); + } else if (n.FunctionDeclaration.check(node.declaration)) { + return new FunctionExportDeclaration(this.module, node); + } else if (n.ExportBatchSpecifier.check(node.specifiers[0])) { + throw new Error( + '`export *` found at ' + sourcePosition(this.module, node) + + ' is not supported, please use `export { … }` instead' + ); + } else { + return new NamedExportDeclaration(this.module, node); + } +}; + +/** + * @param {ast-types.NodePath} referencePath + * @return {?ExportSpecifier} + */ +ExportDeclarationList.prototype.findSpecifierForReference = function(referencePath) { + if (n.ExportSpecifier.check(referencePath.parent.node) && referencePath.parent.parent.node.source) { + // This is a direct export from another module, e.g. `export { foo } from 'foo'`. + return this.findSpecifierByIdentifier(referencePath.node); + } + + var declaration = this.findDeclarationForReference(referencePath); + + if (!declaration) { + return null; + } + + var specifier = this.findSpecifierByName(declaration.node.name); + assert.ok( + specifier, + 'no specifier found for `' + referencePath.node.name + '`! this should not happen!' + ); + return specifier; +}; + +/** + * Contains information about an export declaration. + * + * @constructor + * @abstract + * @extends ModuleBindingDeclaration + * @param {Module} mod + * @param {ast-types.ExportDeclaration} node + */ +function ExportDeclaration(mod, node) { + assert.ok( + n.ExportDeclaration.check(node), + 'expected an export declaration, got ' + (node && node.type) + ); + + ModuleBindingDeclaration.call(this, mod, node); +} +extend(ExportDeclaration, ModuleBindingDeclaration); + +/** + * Returns a string description suitable for debugging. + * + * @return {string} + */ +ExportDeclaration.prototype.inspect = function() { + return recast.print(this.node).code; +}; + +/** + * @alias inspect + */ +ExportDeclaration.prototype.toString = ExportDeclaration.prototype.inspect; + +/** + * Represents an export declaration of the form: + * + * export default foo; + * + * @constructor + * @extends ExportDeclaration + * @param {Module} mod + * @param {ast-types.ExportDeclaration} node + */ +function DefaultExportDeclaration(mod, node) { + ExportDeclaration.call(this, mod, node); +} +extend(DefaultExportDeclaration, ExportDeclaration); + +/** + * Contains a list of specifier name information for this export. + * + * @type {Array.} + * @property specifiers + */ +memo(DefaultExportDeclaration.prototype, 'specifiers', function() { + var specifier = new DefaultExportSpecifier(this, this.node.declaration); + return [specifier]; +}); + +/** + * Represents an export declaration of the form: + * + * export { foo, bar }; + * + * @constructor + * @extends ExportDeclaration + * @param {Module} mod + * @param {ast-types.ExportDeclaration} node + */ +function NamedExportDeclaration(mod, node) { + ExportDeclaration.call(this, mod, node); +} +extend(NamedExportDeclaration, ExportDeclaration); + +/** + * Contains a list of specifier name information for this export. + * + * @type {Array.} + * @property specifiers + */ +memo(NamedExportDeclaration.prototype, 'specifiers', function() { + var self = this; + return this.node.specifiers.map(function(specifier) { + return new ExportSpecifier(self, specifier); + }); +}); + +/** + * Represents an export declaration of the form: + * + * export var foo = 1; + * + * @constructor + * @extends ExportDeclaration + * @param {Module} mod + * @param {ast-types.ExportDeclaration} node + */ +function VariableExportDeclaration(mod, node) { + ExportDeclaration.call(this, mod, node); +} +extend(VariableExportDeclaration, ExportDeclaration); + +/** + */ +memo(VariableExportDeclaration.prototype, 'specifiers', function() { + var self = this; + return this.node.declaration.declarations.map(function(declarator) { + return new ExportSpecifier(self, declarator); + }); +}); + +/** + * Represents an export declaration of the form: + * + * export function foo() {} + * + * @constructor + * @extends ExportDeclaration + * @param {Module} mod + * @param {ast-types.ExportDeclaration} node + */ +function FunctionExportDeclaration(mod, node) { + ExportDeclaration.call(this, mod, node); +} +extend(FunctionExportDeclaration, ExportDeclaration); + +/** + */ +memo(FunctionExportDeclaration.prototype, 'specifiers', function() { + return [new ExportSpecifier(this, this.node.declaration)]; +}); + +/** + * Represents an export specifier in an export declaration. + * + * @constructor + * @extends ModuleBindingSpecifier + * @param {ExportDeclaration} declaration + * @param {ast-types.ExportSpecifier} node + */ +function ExportSpecifier(declaration, node) { + ModuleBindingSpecifier.call(this, declaration, node); +} +extend(ExportSpecifier, ModuleBindingSpecifier); + +/** + * Contains the local declaration info for this export specifier. For example, + * in this module: + * + * var a = 1; + * export { a }; + * + * The module declaration info for the `a` export specifier is the variable + * declaration plus the `a` identifier in its first declarator. + * + * @type {DeclarationInfo} + * @property moduleDeclaration + */ +memo(ExportSpecifier.prototype, 'moduleDeclaration', function() { + if (this.declaration.source) { + // This is part of a direct export, e.g. `export { ... } from '...'`, so + // there is no declaration as part of this module. + return null; + } + + var bindings = this.moduleScope.getBindings(); + var identifierPaths = bindings[this.from]; + assert.ok( + identifierPaths && identifierPaths.length === 1, + 'expected exactly one declaration for export `' + + this.from + '` at ' + sourcePosition(this.module, this.node) + + ', found ' + (identifierPaths ? identifierPaths.length : 'none') + ); + + var identifierPath = identifierPaths[0]; + var declarationInfo = DeclarationInfo.forIdentifierPath(identifierPath); + + assert.ok( + declarationInfo, + 'cannot detect declaration for `' + + identifierPath.node.name + '`, found parent.type `' + + identifierPath.parent.node.type + '`' + ); + + return declarationInfo; +}); + +/** + * Represents an export specifier in a default export declaration. + * + * @constructor + * @extends ExportSpecifier + * @param {ExportDeclaration} declaration + * @param {ast-types.ExportSpecifier} node + */ +function DefaultExportSpecifier(declaration, node) { + ExportSpecifier.call(this, declaration, node); +} +extend(DefaultExportSpecifier, ExportSpecifier); + +/** + * Default export specifier names are always "default". + * + * @type {string} + * @property name + */ +DefaultExportSpecifier.prototype.name = 'default'; + +/** + * Default export specifiers do not bind to a local identifier. + * + * @type {?ast-types.Identifier} + * @property identifier + */ +DefaultExportSpecifier.prototype.identifier = null; + +/** + * Default export specifiers do not have a local bound name. + * + * @type {?string} + * @property from + */ +DefaultExportSpecifier.prototype.from = null; + +/** + * Default export specifiers do not have a local declaration. + * + * @type {?DeclarationInfo} + * @property moduleDeclaration + */ +DefaultExportSpecifier.prototype.moduleDeclaration = null; + +module.exports = ExportDeclarationList; diff --git a/lib/file_resolver.js b/lib/file_resolver.js new file mode 100644 index 0000000..139ae93 --- /dev/null +++ b/lib/file_resolver.js @@ -0,0 +1,78 @@ +/* jshint node:true, undef:true, unused:true */ + +var assert = require('assert'); +var Path = require('path'); +var fs = require('fs'); + +var Module = require('./module'); + +/** + * Provides resolution of absolute paths from module import sources. + * + * @constructor + */ +function FileResolver(paths) { + assert.ok( + paths && paths.length > 0, + 'missing required argument `paths`' + ); + + this.paths = paths.map(function(path) { + return Path.resolve(path); + }); +} + +/** + * Resolves `importedPath` imported by the given module `fromModule` to a + * module. + * + * @param {string} importedPath + * @param {?Module} fromModule + * @param {Container} container + * @return {?Module} + */ +FileResolver.prototype.resolveModule = function(importedPath, fromModule, container) { + var resolvedPath = this.resolvePath(importedPath, fromModule); + if (resolvedPath) { + var cachedModule = container.getCachedModule(resolvedPath); + if (cachedModule) { + return cachedModule; + } else { + return new Module(resolvedPath, importedPath, container); + } + } else { + return null; + } +}; + +/** + * Resolves `importedPath` against the importing module `fromModule`, if given, + * within this resolver's paths. + * + * @private + * @param {string} importedPath + * @param {?Module} fromModule + * @return {string} + */ +FileResolver.prototype.resolvePath = function(importedPath, fromModule) { + var paths = this.paths; + + if (importedPath[0] === '.' && fromModule) { + paths = [Path.dirname(fromModule.path)]; + } + + for (var i = 0, length = paths.length; i < length; i++) { + var includePath = paths[i]; + var resolved = Path.resolve(includePath, importedPath); + if (resolved.slice(-3).toLowerCase() !== '.js') { + resolved += '.js'; + } + if (fs.existsSync(resolved)) { + return resolved; + } + } + + return null; +}; + +module.exports = FileResolver; diff --git a/lib/formatters.js b/lib/formatters.js new file mode 100644 index 0000000..a447e9b --- /dev/null +++ b/lib/formatters.js @@ -0,0 +1,6 @@ +/* jshint node:true, undef:true, unused:true */ + +exports.DEFAULT = 'module-variable'; +exports['module-variable'] = require('./formatters/module_variable_formatter'); +exports['export-variable'] = require('./formatters/export_variable_formatter'); +exports.commonjs = require('./formatters/commonjs_formatter'); diff --git a/lib/formatters/commonjs_formatter.js b/lib/formatters/commonjs_formatter.js new file mode 100644 index 0000000..a7e7ce5 --- /dev/null +++ b/lib/formatters/commonjs_formatter.js @@ -0,0 +1,333 @@ +/* jshint node:true, undef:true, unused:true */ + +var assert = require('assert'); + +var recast = require('recast'); +var types = recast.types; +var n = types.namedTypes; +var b = types.builders; + +var Replacement = require('../replacement'); + +/** + * The 'commonjs' setting for referencing exports aims to produce code that can + * be used in environments using the CommonJS module system, such as Node.js. + * + * @constructor + */ +function CommonJSFormatter() {} + +/** + * Returns an expression which globally references the export named by + * `identifier` for the given module `mod`. For example: + * + * // rsvp/defer.js, export default + * rsvp$defer$$.default + * + * // rsvp/utils.js, export function isFunction + * rsvp$utils$$.isFunction + * + * @param {Module} mod + * @param {ast-types.Identifier} identifier + * @return {ast-types.MemberExpression} + */ +CommonJSFormatter.prototype.reference = function(mod, identifier) { + return b.memberExpression( + b.identifier(mod.id), + n.Identifier.check(identifier) ? identifier : b.identifier(identifier), + false + ); +}; + +/** + * Process a variable declaration found at the top level of the module. Since + * we do not need to rewrite exported variables, we can leave variable + * declarations alone. + * + * @param {Module} mod + * @param {ast-types.NodePath} nodePath + * @return {?Array.} + */ +CommonJSFormatter.prototype.processVariableDeclaration = function(/* mod, nodePath */) { + return null; +}; + +/** + * Process a variable declaration found at the top level of the module. Since + * we do not need to rewrite exported functions, we can leave function + * declarations alone. + * + * @param {Module} mod + * @param {ast-types.NodePath} nodePath + * @returns {Array.} + */ +CommonJSFormatter.prototype.processFunctionDeclaration = function(mod, nodePath) { + return null; +}; + +/** + * Because exported references are captured via a closure as part of a getter + * on the `exports` object, there's no need to rewrite local references to + * exported values. For example, `value` in this example can stay as is: + * + * // a.js + * export var value = 1; + * + * @param {Module} mod + * @param {ast-types.NodePath} referencePath + * @return {ast-types.Expression} + */ +CommonJSFormatter.prototype.exportedReference = function(/* mod, referencePath */) { + return null; +}; + +/** + * Gets a reference to an imported binding by getting the value from the + * required module on demand. For example, this module: + * + * // b.js + * import { value } from './a'; + * console.log(value); + * + * Would be rewritten to look something like this: + * + * var a$$ = require('./a'); + * console.log(a$$.value): + * + * If the given reference does not refer to an imported binding then no + * rewriting is required and `null` will be returned. + * + * @param {Module} mod + * @param {ast-types.NodePath} referencePath + * @return {?ast-types.Expression} + */ +CommonJSFormatter.prototype.importedReference = function(mod, referencePath) { + var specifier = mod.imports.findSpecifierForReference(referencePath); + + if (specifier) { + return this.reference( + specifier.declaration.source, + specifier.from + ); + } else { + return null; + } +}; + +/** + * @param {Module} mod + * @param {ast-types.Expression} declaration + * @return {ast-types.Statement} + */ +CommonJSFormatter.prototype.defaultExport = function(mod, declaration) { + return b.expressionStatement( + b.assignmentExpression( + '=', + b.memberExpression( + b.identifier('exports'), + b.identifier('default'), + false + ), + declaration + ) + ); +}; + +/** + * Replaces non-default exports. For declarations we simply remove the `export` + * keyword. For export declarations that just specify bindings, e.g. + * + * export { a, b }; + * + * we remove them entirely since they'll be handled when we define properties on + * the `exports` object. + * + * @param {Module} mod + * @param {ast-types.NodePath} nodePath + * @return {?Replacement} + */ +CommonJSFormatter.prototype.processExportDeclaration = function(mod, nodePath) { + var node = nodePath.node; + + if (n.FunctionDeclaration.check(node.declaration)) { + return Replacement.swaps(nodePath, node.declaration); + } else if (n.VariableDeclaration.check(node.declaration)) { + return Replacement.swaps(nodePath, node.declaration); + } else if (n.ClassDeclaration.check(node.declaration)) { + return Replacement.swaps(nodePath, node.declaration); + } else if (node.declaration) { + throw new Error('unexpected export style, found a declaration of type: ' + node.declaration.type); + } else { + return Replacement.removes(nodePath); + } +}; + +/** + * Since import declarations only control how we rewrite references we can just + * remove them -- they don't turn into any actual statements. + * + * @param {Module} mod + * @param {ast-types.NodePath} nodePath + * @return {?Replacement} + */ +CommonJSFormatter.prototype.processImportDeclaration = function(mod, nodePath) { + return Replacement.removes(nodePath); +}; + +/** + * Convert a list of ordered modules into a list of files. + * + * @param {Array.} modules Modules in execution order. + * @return {Array.= 0) { + return; + } + requiredModules.push(sourceModule); + + var matchingDeclaration; + declarations.declarations.some(function(declaration) { + if (declaration.source === sourceModule) { + matchingDeclaration = declaration; + return true; + } + }); + + assert.ok( + matchingDeclaration, + 'no matching declaration for source module: ' + sourceModule.relativePath + ); + + // `(import|export) { ... } from 'math'` -> `math$$ = require('math')` + declarators.push(b.variableDeclarator( + b.identifier(sourceModule.id), + b.callExpression( + b.identifier('require'), + [b.literal(matchingDeclaration.sourcePath)] + ) + )); + }); + }); + + if (declarators.length > 0) { + return b.variableDeclaration('var', declarators); + } else { + return b.emptyStatement(); + } +}; + +/** + * @private + * @param {Module} mod + * @return {ast-types.Statement} + */ +CommonJSFormatter.prototype.buildExports = function(mod) { + var self = this; + var properties = []; + + mod.exports.names.forEach(function(name) { + var specifier = mod.exports.findSpecifierByName(name); + + assert.ok( + specifier, + 'no export specifier found for export name `' + + name + '` from ' + mod.relativePath + ); + + if (!specifier.from) { + return; + } + + var from = + specifier.importSpecifier ? + self.reference( + specifier.importSpecifier.declaration.source, + specifier.importSpecifier.from + ) : + specifier.declaration.source ? + self.reference( + specifier.declaration.source, + specifier.name + ) : + b.identifier(specifier.from); + + properties.push(b.property( + 'init', + b.identifier(name), + b.objectExpression([ + // Simulate named export bindings with a getter. + b.property( + 'init', + b.identifier('get'), + b.functionExpression( + null, + [], + b.blockStatement([b.returnStatement(from)]) + ) + ), + b.property( + 'init', + b.identifier('enumerable'), + b.literal(true) + ) + ]) + )); + }); + + var exportObject = b.identifier('exports'); + + if (properties.length > 0) { + exportObject = b.callExpression( + b.memberExpression( + b.identifier('Object'), + b.identifier('defineProperties'), + false + ), + [ + exportObject, + b.objectExpression(properties) + ] + ); + } + + return b.expressionStatement( + b.callExpression( + b.memberExpression( + b.identifier('Object'), + b.identifier('seal'), + false + ), + [exportObject] + ) + ); +}; + +module.exports = CommonJSFormatter; diff --git a/lib/formatters/export_variable_formatter.js b/lib/formatters/export_variable_formatter.js new file mode 100644 index 0000000..57cb189 --- /dev/null +++ b/lib/formatters/export_variable_formatter.js @@ -0,0 +1,102 @@ +/* jshint node:true, undef:true, unused:true */ + +var recast = require('recast'); +var types = recast.types; +var n = types.namedTypes; +var b = types.builders; + +var utils = require('../utils'); +var extend = utils.extend; + +var VariableFormatterBase = require('./variable_formatter_base'); + +/** + * The 'export-variable' setting for referencing exports aims to increase the + * compressability of the generated source, especially by tools such as Google + * Closure Compiler or UglifyJS. For example, given these modules: + * + * // a.js + * import { b } from './b'; + * console.log(b); + * + * // b.js + * export var b = 3; + * export var b2 = 6; + * + * The final output will be a single file looking something like this: + * + * (function() { + * var b$$b, b$$b2; + * + * (function() { + * // b.js + * b$$b = 3; + * b$$b2 = 6; + * })(); + * + * (function() { + * // a.js + * console.log(b$$b); + * })(); + * })(); + * + * @constructor + * @extends VariableFormatterBase + */ +function ExportVariableFormatter() {} +extend(ExportVariableFormatter, VariableFormatterBase); + +/** + * Returns an expression which globally references the export named by + * `identifier` for the given module `mod`. For example: + * + * // rsvp/defer.js, export default + * rsvp$defer$$default + * + * // rsvp/utils.js, export function isFunction + * rsvp$utils$$isFunction + * + * @param {Module} mod + * @param {ast-types.Identifier|string} identifier + * @return {ast-types.Identifier} + */ +ExportVariableFormatter.prototype.reference = function(mod, identifier) { + return b.identifier( + mod.id + (n.Identifier.check(identifier) ? identifier.name : identifier) + ); +}; + +/** + * Returns a declaration for the exports for the given module. In this case, + * it will have one declarator per exported name, prefixed with the module's + * id. For example: + * + * // rsvp/defer.js, export default + * var rsvp$defer$$default; + * + * // rsvp/utils.js, export function isFunction + * var rsvp$utils$$isFunction; + * + * @param {Array.} modules + * @return {ast-types.VariableDeclaration} + */ +ExportVariableFormatter.prototype.variableDeclaration = function(modules) { + var declarators = []; + var self = this; + + modules.forEach(function(mod) { + mod.exports.names.forEach(function(name) { + declarators.push( + b.variableDeclarator(self.reference(mod, name), null) + ); + }); + }); + + if (declarators.length > 0) { + return b.variableDeclaration('var', declarators); + } else { + return b.emptyStatement(); + } +}; + +module.exports = ExportVariableFormatter; diff --git a/lib/formatters/module_variable_formatter.js b/lib/formatters/module_variable_formatter.js new file mode 100644 index 0000000..ec503f9 --- /dev/null +++ b/lib/formatters/module_variable_formatter.js @@ -0,0 +1,97 @@ +/* jshint node:true, undef:true, unused:true */ + +var recast = require('recast'); +var types = recast.types; +var n = types.namedTypes; +var b = types.builders; + +var utils = require('../utils'); +var extend = utils.extend; + +var VariableFormatterBase = require('./variable_formatter_base'); + +/** + * The 'module-variable' setting for referencing exports aims to reduce the + * number of variables in the outermost IFFE scope. This avoids potentially + * quadratic performance degradations as shown by + * https://gist.github.com/joliss/9331281. For example, given these modules: + * + * // a.js + * import { b } from './b'; + * console.log(b); + * + * // b.js + * export var b = 3; + * export var b2 = 6; + * + * The final output will be a single file looking something like this: + * + * (function() { + * var b$$ = {}; + * + * (function() { + * // b.js + * b$$.b = 3; + * b$$.b2 = 6; + * })(); + * + * (function() { + * // a.js + * console.log(b$$.b); + * })(); + * })(); + * + * @constructor + * @extends VariableFormatterBase + */ +function ModuleVariableFormatter() {} +extend(ModuleVariableFormatter, VariableFormatterBase); + +/** + * Returns an expression which globally references the export named by + * `identifier` for the given module `mod`. For example: + * + * // rsvp/defer.js, export default + * rsvp$defer$$.default + * + * // rsvp/utils.js, export function isFunction + * rsvp$utils$$.isFunction + * + * @param {Module} mod + * @param {ast-types.Identifier} identifier + * @return {ast-types.MemberExpression} + */ +ModuleVariableFormatter.prototype.reference = function(mod, identifier) { + return b.memberExpression( + b.identifier(mod.id), + n.Identifier.check(identifier) ? identifier : b.identifier(identifier), + false + ); +}; + +/** + * Returns a declaration for the exports for the given modules. In this case, + * this is always just the the module objects (e.g. `var rsvp$defer$$ = {};`). + * + * @param {Array.} modules + * @return {ast-types.VariableDeclaration} + */ +ModuleVariableFormatter.prototype.variableDeclaration = function(modules) { + var declarators = []; + + modules.forEach(function(mod) { + if (mod.exports.names.length > 0) { + declarators.push( + b.variableDeclarator(b.identifier(mod.id), b.objectExpression([])) + ); + } + }); + + if (declarators.length > 0) { + return b.variableDeclaration('var', declarators); + } else { + return b.emptyStatement(); + } +}; + +module.exports = ModuleVariableFormatter; diff --git a/lib/formatters/variable_formatter_base.js b/lib/formatters/variable_formatter_base.js new file mode 100644 index 0000000..d0d0f23 --- /dev/null +++ b/lib/formatters/variable_formatter_base.js @@ -0,0 +1,396 @@ +/* jshint node:true, undef:true, unused:true */ + +var recast = require('recast'); +var types = recast.types; +var b = types.builders; +var n = types.namedTypes; + +var Replacement = require('../replacement'); +var utils = require('../utils'); +var IFFE = utils.IFFE; +var sort = require('../sorting').sort; + + +/** + * The variable class of export strategies concatenate all modules together, + * isolating their local variables. All exports and imports are rewritten to + * reference variables in a shared parent scope. For example, given these + * modules: + * + * // a.js + * import { b } from './b'; + * + * // b.js + * export var b = 3; + * + * The final output will be a single file looking something like this: + * + * (function() { + * // variable declarations here + * + * (function() { + * // b.js, sets variables declared above + * })(); + * + * (function() { + * // a.js, references variables declared above + * })(); + * })(); + * + * @constructor + */ +function VariableFormatterBase() {} + +/** + * This hook is called by the container before it converts its modules. We use + * it to ensure all of the imports are included because we need to know about + * them at compile time. + * + * @param {Container} container + */ +VariableFormatterBase.prototype.beforeConvert = function(container) { + container.findImportedModules(); +}; + +/** + * Returns an expression which globally references the export named by + * `identifier` for the given module `mod`. + * + * @param {Module} mod + * @param {ast-types.Identifier|string} identifier + * @return {ast-types.MemberExpression} + */ +VariableFormatterBase.prototype.reference = function(/* mod, identifier */) { + throw new Error('#reference must be implemented in subclasses'); +}; + +/** + * Process a variable declaration found at the top level of the module. We need + * to ensure that exported variables are rewritten appropriately, so we may + * need to rewrite some or all of this variable declaration. For example: + * + * var a = 1, b, c = 3; + * ... + * export { a, b }; + * + * We turn those being exported into assignments as needed, e.g. + * + * var c = 3; + * mod$$a = 1; + * + * @param {Module} mod + * @param {ast-types.NodePath} nodePath + * @return {?Replacement} + */ +VariableFormatterBase.prototype.processVariableDeclaration = function(mod, nodePath) { + var exports = mod.exports; + var node = nodePath.node; + var self = this; + var declarators = []; + var assignments = []; + var exportSpecifier; + + node.declarations.forEach(function(declarator) { + exportSpecifier = exports.findSpecifierByName(declarator.id.name); + if (exportSpecifier) { + // This variable is exported, turn it into a normal assignment. + if (declarator.init) { + // But only if we have something to assign with. + assignments.push( + b.assignmentExpression( + '=', + self.reference(mod, declarator.id), + declarator.init + ) + ); + } + } else { + // This variable is not exported, so keep it. + declarators.push(declarator); + } + }); + + // Don't bother replacing it if we kept all declarators. + if (node.declarations.length === declarators.length) { + return null; + } + + var nodes = []; + + // Do we need a variable declaration at all? + if (declarators.length > 0) { + nodes.push( + b.variableDeclaration(node.kind, declarators) + ); + } + + // Do we have any assignments to add? + if (assignments.length > 0) { + nodes.push(b.expressionStatement( + b.sequenceExpression(assignments) + )); + } + return Replacement.swaps(nodePath, nodes); +}; + +/** + * Appends an assignment after a function declaration if that function is exported. For example, + * + * function foo() {} + * // ... + * export { foo }; + * + * Becomes e.g. + * + * function foo() {} + * mod$$foo = foo; + * + * @param {Module} mod + * @param {ast-types.NodePath} nodePath + * @return {?Replacement} + */ +VariableFormatterBase.prototype.processFunctionDeclaration = function(mod, nodePath) { + var exports = mod.exports; + var node = nodePath.node; + var exportSpecifier = exports.findSpecifierByName(node.id.name); + + // No need for an assignment to an export if it isn't exported. + if (!exportSpecifier) { + return null; + } + + // Add an assignment, e.g. `mod$$foo = foo`. + var assignment = b.expressionStatement( + b.assignmentExpression( + '=', + this.reference(mod, exportSpecifier.name), + node.id + ) + ); + + return Replacement.adds(nodePath, assignment); +}; + +/** + * Replaces non-default exports. Since we mostly rewrite references we may need + * to deconstruct variable declarations. Exported bindings do not need to be + * replaced with actual statements since they only control reference rewrites. + * + * @param {Module} mod + * @param {ast-types.NodePath} nodePath + * @return {?Replacement} + */ +VariableFormatterBase.prototype.processExportDeclaration = function(mod, nodePath) { + var node = nodePath.node; + var self = this; + if (n.FunctionDeclaration.check(node.declaration)) { + /** + * Function exports are declarations and so should not be + * re-assignable, so we can safely turn remove the `export` keyword and + * add an assignment. For example: + * + * export function add(a, b) { return a + b; } + * + * Becomes: + * + * function add(a, b) { return a + b; } + * mod$$.add = add; + */ + return Replacement.swaps( + nodePath, + [ + node.declaration, + b.expressionStatement( + b.assignmentExpression( + '=', + this.reference(mod, node.declaration.id), + node.declaration.id + ) + ) + ] + ); + } else if (n.VariableDeclaration.check(node.declaration)) { + /** + * Variable exports can be re-assigned, so we need to rewrite the + * names. For example: + * + * export var a = 1, b = 2; + * + * Becomes: + * + * mod$$.a = 1, mod$$.b = 2; + */ + return Replacement.swaps( + nodePath, + b.expressionStatement( + b.sequenceExpression( + node.declaration.declarations.reduce(function(assignments, declarator) { + if (declarator.init) { + assignments.push( + b.assignmentExpression( + '=', + self.reference(mod, declarator.id), + declarator.init + ) + ); + } + return assignments; + }, []) + ) + ) + ); + } else if (node.declaration) { + throw new Error('unexpected export style, found a declaration of type: ' + node.declaration.type); + } else { + /** + * For exports with a named specifier list we handle them by re-writing + * their declaration (if it's in this module) and their references, so + * the export declaration can be safely removed. + */ + return Replacement.removes(nodePath); + } +}; + +/** + * Since import declarations only control how we rewrite references we can just + * remove them -- they don't turn into any actual statements. + * + * @param {Module} mod + * @param {ast-types.NodePath} nodePath + * @return {?Replacement} + */ +VariableFormatterBase.prototype.processImportDeclaration = function(mod, nodePath) { + return Replacement.removes(nodePath); +}; + +/** + * Get a reference to the original exported value referenced in `mod` at + * `referencePath`. If the given reference path does not correspond to an + * export, we do not need to rewrite the reference. For example, since `value` + * is not exported it does not need to be rewritten: + * + * // a.js + * var value = 99; + * console.log(value); + * + * If `value` was exported then we would need to rewrite it: + * + * // a.js + * export var value = 3; + * console.log(value); + * + * In this case we re-write both `value` references to someting like + * `a$$value`. The tricky part happens when we re-export an imported binding: + * + * // a.js + * export var value = 11; + * + * // b.js + * import { value } from './a'; + * export { value }; + * + * // c.js + * import { value } from './b'; + * console.log(value); + * + * The `value` reference in a.js will be rewritten as something like `a$$value` + * as expected. The `value` reference in c.js will not be rewritten as + * `b$$value` despite the fact that it is imported from b.js. This is because + * we must follow the binding through to its import from a.js. Thus, our + * `value` references will both be rewritten to `a$$value` to ensure they + * match. + * + * @param {Module} mod + * @param {ast-types.NodePath} referencePath + * @return {ast-types.Expression} + */ +VariableFormatterBase.prototype.exportedReference = function(mod, referencePath) { + var specifier = mod.exports.findSpecifierForReference(referencePath); + if (specifier) { + specifier = specifier.terminalExportSpecifier; + return this.reference(specifier.module, specifier.name); + } else { + return null; + } +}; + +/** + * Get a reference to the original exported value referenced in `mod` at + * `referencePath`. This is very similar to {#exportedReference} in its + * approach. + * + * @param {Module} mod + * @param {ast-types.NodePath} referencePath + * @return {ast-types.Expression} + * @see {#exportedReference} + */ +VariableFormatterBase.prototype.importedReference = function(mod, referencePath) { + var specifier = mod.imports.findSpecifierForReference(referencePath); + if (specifier) { + specifier = specifier.terminalExportSpecifier; + return this.reference(specifier.module, specifier.name); + } else { + return null; + } +}; + +/** + * Convert a list of ordered modules into a list of files. + * + * @param {Array.} modules Modules in execution order. + * @return {Array.} modules + * @return {ast-types.VariableDeclaration} + */ +VariableFormatterBase.prototype.variableDeclaration = function(modules) { + var declarators = []; + + modules.forEach(function(mod) { + if (mod.exports.names.length > 0) { + declarators.push( + b.variableDeclarator(b.identifier(mod.id), b.objectExpression([])) + ); + } + }); + + if (declarators.length > 0) { + return b.variableDeclaration('var', declarators); + } else { + return b.emptyStatement(); + } +}; + +module.exports = VariableFormatterBase; diff --git a/lib/globals_compiler.js b/lib/globals_compiler.js deleted file mode 100644 index 8680902..0000000 --- a/lib/globals_compiler.js +++ /dev/null @@ -1,123 +0,0 @@ -import AbstractCompiler from './abstract_compiler'; -import SourceModifier from './source_modifier'; - -class GlobalsCompiler extends AbstractCompiler { - stringify() { - var string = this.string.toString(); // string is actually a node buffer - this.source = new SourceModifier(string); - - this.map = []; - var out = this.buildPreamble(this.exports.length > 0); - - this.buildImports(); - this.buildExports(); - - if (!this.options.imports) this.options.imports = {}; - if (!this.options.global) this.options.global = "window"; - - out += this.indentLines(); - out += "\n})"; - out += this.buildSuffix(); - out += ";"; - - return out; - } - - buildPreamble() { - var out = "", - dependencyNames = this.dependencyNames; - - out += "(function("; - - if (this.exports.length > 0) { - out += "__exports__"; - if (this.dependencyNames.length > 0) { - out += ', '; - } - } - - for (var idx = 0; idx < dependencyNames.length; idx++) { - out += `__dependency${idx+1}__`; - this.map[dependencyNames[idx]] = idx+1; - if (!(idx === dependencyNames.length - 1)) out += ", "; - } - - out += ") {\n"; - - out += ' "use strict";\n'; - - return out; - } - - buildSuffix() { - var dependencyNames = this.dependencyNames; - var out = "("; - - if (this.exports.length > 0) { - if (this.options.into) { - out += `${this.options.global}.${this.options.into} = {}`; - } else { - out += this.options.global; - } - if (this.dependencyNames.length > 0) { - out += ', '; - } - } - - for (var idx = 0; idx < dependencyNames.length; idx++) { - var name = dependencyNames[idx]; - out += `${this.options.global}.${this.options.imports[name] || name}`; - if (!(idx === dependencyNames.length - 1)) out += ", "; - } - - out += ")"; - return out; - } - - doModuleImport(name, dependencyName, idx) { - return `var ${name} = __dependency${this.map[dependencyName]}__;\n`; - } - - doBareImport(name) { - return ""; - } - - doDefaultImport(name, dependencyName, idx) { - return `var ${name} = __dependency${this.map[dependencyName]}__;\n`; - } - - doNamedImport(name, dependencyName, alias) { - return `var ${alias} = __dependency${this.map[dependencyName]}__.${name};\n`; - } - - doExportSpecifier(name, reexport) { - if (reexport) { - return `__exports__.${name} = __dependency${this.map[reexport]}__.${name};\n`; - } - return `__exports__.${name} = ${name};\n`; - } - - doExportDeclaration(name) { - return `\n__exports__.${name} = ${name};`; - } - - doDefaultExport(identifier) { - if (identifier === null) { - throw new Error("The globals compiler does not support anonymous default exports."); - } - return `__exports__.${identifier} = `; - } - - doImportSpecifiers(import_, idx) { - var dependencyName = import_.source.value; - var replacement = ""; - for (var specifier of import_.specifiers) { - var alias = specifier.name ? specifier.name.name : specifier.id.name; - replacement += this.doNamedImport(specifier.id.name, dependencyName, alias); - } - return replacement; - } - -} - -export default GlobalsCompiler; diff --git a/lib/imports.js b/lib/imports.js new file mode 100644 index 0000000..cab799c --- /dev/null +++ b/lib/imports.js @@ -0,0 +1,213 @@ +/* jshint node:true, undef:true, unused:true */ + +var assert = require('assert'); +var recast = require('recast'); +var types = recast.types; +var n = types.namedTypes; + +var ModuleBindingList = require('./module_binding_list'); +var ModuleBindingDeclaration = require('./module_binding_declaration'); +var ModuleBindingSpecifier = require('./module_binding_specifier'); + +var utils = require('./utils'); +var memo = utils.memo; +var extend = utils.extend; +var sourcePosition = utils.sourcePosition; + +/** + * Represents a list of the imports for the given module. + * + * @constructor + * @param {Module} mod + */ +function ImportDeclarationList(mod) { + ModuleBindingList.call(this, mod); +} +extend(ImportDeclarationList, ModuleBindingList); + +/** + * @private + * @param {ast-types.Node} node + * @return {boolean} + */ +ImportDeclarationList.prototype.isMatchingBinding = function(node) { + return n.ImportDeclaration.check(node); +}; + +/** + * Gets an import declaration for the given `node`. + * + * @private + * @param {ast-types.ImportDeclaration} node + * @return {Import} + */ +ImportDeclarationList.prototype.declarationForNode = function(node) { + switch (node.kind) { + case 'default': + return new DefaultImportDeclaration(this.module, node); + + case 'named': + return new NamedImportDeclaration(this.module, node); + + case undefined: + return new BareImportDeclaration(this.module, node); + + default: + assert.ok(false, 'unexpected import kind at ' + sourcePosition(this.module, node) + ': ' + node.kind); + break; + } +}; + +/** + * Contains information about an import declaration. + * + * @constructor + * @abstract + * @param {Module} mod + * @param {ast-types.ImportDeclaration} node + */ +function ImportDeclaration(mod, node) { + assert.ok( + n.ImportDeclaration.check(node), + 'expected an import declaration, got ' + (node && node.type) + ); + + Object.defineProperties(this, { + node: { + value: node, + enumerable: false + }, + + module: { + value: mod, + enumerable: false + } + }); +} +extend(ImportDeclaration, ModuleBindingDeclaration); + +/** + * Represents a default import of the form + * + * import List from 'list'; + * + * @constructor + * @extends ImportDeclaration + * @param {Module} mod + * @param {ast-types.ImportDeclaration} node + */ +function DefaultImportDeclaration(mod, node) { + assert.equal(node.kind, 'default'); + assert.ok( + node.specifiers.length === 1 && node.specifiers[0], + 'expected exactly one specifier for a default import, got ' + + node.specifiers.length + ); + + ImportDeclaration.call(this, mod, node); +} +extend(DefaultImportDeclaration, ImportDeclaration); + +/** + * Gets a reference to the exported value from this import's module that + * corresponds to the local binding created by this import with the name given + * by `identfier`. + * + * @param {ast-types.Identifier|string} identifier + * @return {ast-types.Expression} + */ +DefaultImportDeclaration.prototype.getExportReference = function(identifier) { + var name = n.Identifier.check(identifier) ? identifier.name : identifier; + assert.equal( + name, + this.node.specifiers[0].id.name, + 'no export specifier found for `' + name + '`' + ); + return this.module.getExportReference('default'); +}; + +/** + * Contains a list of specifier name information for this import. + * + * @type {Array.} + * @property specifiers + */ +memo(DefaultImportDeclaration.prototype, 'specifiers', function() { + var specifier = new ImportSpecifier(this, this.node.specifiers[0]); + specifier.from = 'default'; + assert.equal(specifier.from, 'default'); + return [specifier]; +}); + +/** + * Represents a named import of the form + * + * import { sin, cos } from 'math'; + * + * @constructor + * @extends ImportDeclaration + * @param {Module} mod + * @param {ast-types.ImportDeclaration} node + */ +function NamedImportDeclaration(mod, node) { + assert.equal(node.kind, 'named'); + ImportDeclaration.call(this, mod, node); +} +extend(NamedImportDeclaration, ImportDeclaration); + +/** + * Contains a list of specifier name information for this import. + * + * @type {Array.} + * @property specifiers + */ +memo(NamedImportDeclaration.prototype, 'specifiers', function() { + var self = this; + return this.node.specifiers.map(function(specifier) { + return new ImportSpecifier(self, specifier); + }); +}); + +/** + * Represents an import with no bindings created in the local scope. These + * imports are of the form `import 'path/to/module'` and are generally included + * only for their side effects. + * + * @constructor + * @extends ImportDeclaration + * @param {Module} mod + * @param {ast-types.ImportDeclaration} node + */ +function BareImportDeclaration(mod, node) { + assert.ok( + node.kind === undefined && node.specifiers.length === 0, + 'expected a bare import at ' + sourcePosition(mod, node) + + ', got one with kind=' + node.kind + ' and ' + + node.specifiers.length + ' specifier(s)' + ); + ImportDeclaration.call(this, mod, node); +} +extend(BareImportDeclaration, ImportDeclaration); + +/** + * Returns an empty set of specifiers. + * + * @type {Array.} + * @property specifiers + */ +memo(BareImportDeclaration.prototype, 'specifiers', function() { + return []; +}); + +/** + */ +function ImportSpecifier(declaration, node) { + assert.ok( + declaration instanceof ImportDeclaration, + 'expected an instance of ImportDeclaration' + ); + ModuleBindingSpecifier.call(this, declaration, node); +} +extend(ImportSpecifier, ModuleBindingSpecifier); + +module.exports = ImportDeclarationList; diff --git a/lib/index.js b/lib/index.js index 6cba436..0465ab1 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,13 +1,11 @@ -import Compiler from './compiler'; +/* jshint node:true, undef:true, unused:true */ -import AbstractCompiler from './abstract_compiler'; -import AmdCompiler from './amd_compiler'; -import YuiCompiler from './yui_compiler'; -import CjsCompiler from './cjs_compiler'; -import GlobalsCompiler from './globals_compiler'; -import SourceModifier from './source_modifier'; +var Container = require('./container'); +var FileResolver = require('./file_resolver'); -export { Compiler }; +exports.FileResolver = FileResolver; +exports.Container = Container; -// Building blocks/subclassing APIs -export { AbstractCompiler, AmdCompiler, YuiCompiler, CjsCompiler, GlobalsCompiler, SourceModifier }; +exports.makeContainer = function(options) { + return new Container(options); +}; diff --git a/lib/module.js b/lib/module.js new file mode 100644 index 0000000..aa0e877 --- /dev/null +++ b/lib/module.js @@ -0,0 +1,193 @@ +/* jshint node:true, undef:true, unused:true */ + +var assert = require('assert'); +var fs = require('fs'); + +var esprima = require('esprima'); +var recast = require('recast'); +var types = recast.types; +var n = types.namedTypes; +var b = types.builders; +var NodePath = recast.types.NodePath; + +var ImportDeclarationList = require('./imports'); +var ExportDeclarationList = require('./exports'); +var utils = require('./utils'); +var memo = utils.memo; +var endsWith = utils.endsWith; + +function Module(path, relativePath, container) { + Object.defineProperties(this, { + path: { + value: path, + enumerable: true, + writable: false + }, + + relativePath: { + value: relativePath, + enumerable: true, + writable: false + }, + + container: { + value: container, + enumerable: true, + writable: false + } + }); +} + +/** + * Clears the cached data for this module. + */ +Module.prototype.reload = function() { + delete this.ast; + delete this.imports; + delete this.exports; + delete this.scope; +}; + +/** + * The list of imports declared by this module. + * + * @type {ImportDeclarationList} + * @property imports + */ +memo(Module.prototype, 'imports', function() { + var result = new ImportDeclarationList(this); + result.readProgram(this.ast.program); + return result; +}); + +/** + * The list of exports declared by this module. + * + * @type {ExportDeclarationList} + * @property exports + */ +memo(Module.prototype, 'exports', function() { + var result = new ExportDeclarationList(this); + result.readProgram(this.ast.program); + return result; +}); + +/** + * This module's scope. + * + * @type {ast-types.Scope} + * @property scope + */ +memo(Module.prototype, 'scope', function() { + return new NodePath(this.ast).get('program').get('body').scope; +}); + +/** + * This module's source code represented as an abstract syntax tree. + * + * @type {ast-types.File} + * @property ast + */ +memo(Module.prototype, 'ast', function() { + return recast.parse( + fs.readFileSync(this.path).toString(), + { esprima: esprima } + ); +}); + +/** + * A reference to the options from this module's container. + * + * @type {object} + * @property options + */ +memo(Module.prototype, 'options', function() { + return this.container.options; +}); + +/** + * This module's relative name, like {#relativePath} but without the extension. + * + * @type {string} + * @property name + */ +memo(Module.prototype, 'name', function() { + var relativePath = this.relativePath; + if (endsWith(relativePath, '.js')) { + return relativePath.slice(0, -3); + } else { + return relativePath; + } +}); + +/** + * A string suitable for a JavaScript identifier named for this module. + * + * @type {string} + * @property id + */ +memo(Module.prototype, 'id', function() { + return this.name.replace(/[^\w$_]/g, '$') + '$$'; +}); + +/** + * Gets a Module by path relative to this module. + * + * @param {string} sourcePath + * @return {Module} + */ +Module.prototype.getModule = function(sourcePath) { + return this.container.getModule(sourcePath, this); +}; + +/** + */ +Module.prototype.getExportReference = function(identifier) { + var name; + if (n.Identifier.check(identifier)) { + name = identifier.name; + } else { + name = identifier; + identifier = null; + } + assert.equal(typeof name, 'string'); + + // TODO: Use constant for 'compression'. + if (this.options.optimize === 'compression') { + return b.identifier(this.id + name); + } else { + return b.memberExpression( + b.identifier(this.id), + identifier || b.identifier(name), + false + ); + } +}; + +/** + * Gets a reference to the original exported value corresponding to the local + * binding created by this import with the name given by `identfier`. + * + * @param {ast-types.NodePath} referencePath + * @return {ast-types.Expression} + */ +Module.prototype.getBindingReference = function(referencePath) { + var imp = this.imports.findImportForReference(referencePath); + return imp; +}; + +/** + * Generate a descriptive string suitable for debugging. + * + * @return {string} + */ +Module.prototype.inspect = function() { + return '#<' + this.constructor.name + ' ' + this.relativePath + '>'; +}; + +/** + * @alias {#inspect} + */ +Module.prototype.toString = Module.prototype.inspect; + +module.exports = Module; diff --git a/lib/module_binding_declaration.js b/lib/module_binding_declaration.js new file mode 100644 index 0000000..1411362 --- /dev/null +++ b/lib/module_binding_declaration.js @@ -0,0 +1,116 @@ +/* jshint node:true, undef:true, unused:true */ + +var assert = require('assert'); + +var recast = require('recast'); +var types = recast.types; +var n = types.namedTypes; + +var utils = require('./utils'); +var memo = utils.memo; + +/** + * Contains information about a module binding declaration. This corresponds to + * the shared functionality of `ExportDeclaration` and `ImportDeclaration` in + * the ES6 spec. + * + * @constructor + * @abstract + * @param {Module} mod + * @param {ast-types.ImportDeclaration|ast-types.ExportDeclaration} node + */ +function ModuleBindingDeclaration(mod, node) { + assert.ok( + n.ImportDeclaration.check(node) || n.ExportDeclaration.check(node), + 'expected an import or export declaration, got ' + (node && node.type) + ); + + Object.defineProperties(this, { + node: { + value: node, + enumerable: false + }, + + module: { + value: mod, + enumerable: false + } + }); +} + +/** + * Finds the specifier that creates the local binding given by `name`, if one + * exists. Otherwise `null` is returned. + * + * @private + * @param {string} name + * @return {?ModuleBindingSpecifier} + */ +ModuleBindingDeclaration.prototype.findSpecifierByName = function(name) { + var specifiers = this.specifiers; + + for (var i = 0, length = specifiers.length; i < length; i++) { + var specifier = specifiers[i]; + if (specifier.name === name) { + return specifier; + } + } + + return null; +}; + +/** + * @private + * @param {ast-types.Identifier} identifier + * @return {?ModuleBindingSpecifier} + */ +ModuleBindingDeclaration.prototype.findSpecifierByIdentifier = function(identifier) { + for (var i = 0, length = this.specifiers.length; i < length; i++) { + var specifier = this.specifiers[i]; + if (specifier.identifier === identifier) { + return specifier; + } + } + + return null; +}; + +memo(ModuleBindingDeclaration.prototype, 'sourcePath', function() { + return this.node.source && this.node.source.value; +}); + +/** + * Gets a reference to the module referenced by this declaration. + * + * @type {Module} + * @property source + */ +memo(ModuleBindingDeclaration.prototype, 'source', function() { + return this.sourcePath ? this.module.getModule(this.sourcePath) : null; +}); + +/** + * Gets the module scope. + * + * @type {ast-types.Scope} + * @property scope + */ +memo(ModuleBindingDeclaration.prototype, 'moduleScope', function() { + return this.module.scope; +}); + +/** + * Generate a string representing this object to aid debugging. + * + * @return {string} + */ +ModuleBindingDeclaration.prototype.inspect = function() { + return recast.print(this.node).code; +}; + +/** + * @alias {#inspect} + */ +ModuleBindingDeclaration.prototype.toString = ModuleBindingDeclaration.prototype.inspect; + +module.exports = ModuleBindingDeclaration; diff --git a/lib/module_binding_list.js b/lib/module_binding_list.js new file mode 100644 index 0000000..ada2b25 --- /dev/null +++ b/lib/module_binding_list.js @@ -0,0 +1,233 @@ +/* jshint node:true, undef:true, unused:true */ + +var assert = require('assert'); + +var utils = require('./utils'); +var memo = utils.memo; +var sourcePosition = utils.sourcePosition; + +/** + * Represents a list of bindings for the given module. This corresponds to the + * shared functionality from `ExportsList` and `ImportsList` from the ES6 spec. + * + * @abstract + * @constructor + * @param {Module} mod + */ +function ModuleBindingList(mod) { + Object.defineProperties(this, { + _nodes: { + value: [], + enumerable: false + }, + + module: { + value: mod, + enumerable: false + } + }); +} + +/** + * Add all the binding declarations from the given scope body. Generally this + * should be the Program node's `body` property, an array of statements. + * + * @param {ast-types.Program} program + */ +ModuleBindingList.prototype.readProgram = function(program) { + var body = program.body; + for (var i = 0; i < body.length; i++) { + if (this.isMatchingBinding(body[i])) { + this.addDeclaration(body[i]); + } + } +}; + +/** + * Adds a declaration to the list. + * + * @private + * @param {ast-types.ImportDeclaration|ast-types.ExportDeclaration} node + */ +ModuleBindingList.prototype.addDeclaration = function(node) { + assert.ok( + this.isMatchingBinding(node), + 'expected node to be an declaration, but got ' + + (node && node.type) + ); + this._nodes.push(node); + + // reset the cache + delete this.declarations; + delete this.specifiers; + delete this.modules; +}; + +/** + * Gets the module scope. + * + * @type {ast-types.Scope} + * @property scope + */ +memo(ModuleBindingList.prototype, 'moduleScope', function() { + return this.module.scope; +}); + +/** + * Gets all the modules referenced by the declarations in this list. + * + * @type {Array.} + * @property modules + */ +memo(ModuleBindingList.prototype, 'modules', function() { + var modules = []; + + this.declarations.forEach(function(declaration) { + if (declaration.source && modules.indexOf(declaration.source) < 0) { + modules.push(declaration.source); + } + }); + + return modules; +}); + +/** + * Finds the specifier that creates the local binding given by `name`, if one + * exists. Otherwise `null` is returned. + * + * @private + * @param {string} name + * @return {?ModuleBindingSpecifier} + */ +ModuleBindingList.prototype.findSpecifierByName = function(name) { + for (var i = 0, length = this.declarations.length; i < length; i++) { + var specifier = this.declarations[i].findSpecifierByName(name); + if (specifier) { return specifier; } + } + + return null; +}; + +/** + * @private + * @param {ast-types.Identifier} identifier + * @return {?ModuleBindingSpecifier} + */ +ModuleBindingList.prototype.findSpecifierByIdentifier = function(identifier) { + for (var i = 0, length = this.declarations.length; i < length; i++) { + var specifier = this.declarations[i].findSpecifierByIdentifier(identifier); + if (specifier && specifier.identifier === identifier) { + return specifier; + } + } + + return null; +}; + +/** + * @param {ast-types.NodePath} referencePath + * @return {?ModuleBindingSpecifier} + */ +ModuleBindingList.prototype.findSpecifierForReference = function(referencePath) { + var declaration = this.findDeclarationForReference(referencePath); + + if (!declaration) { + return null; + } + + var specifier = this.findSpecifierByIdentifier(declaration.node); + assert.ok( + specifier, + 'no specifier found for `' + referencePath.node.name + '`! this should not happen!' + ); + return specifier; +}; + +/** + * @private + */ +ModuleBindingList.prototype.findDeclarationForReference = function(referencePath) { + // Check names to avoid traversing scopes for all references. + if (this.names.indexOf(referencePath.node.name) < 0) { + return null; + } + + var node = referencePath.node; + var declaringScope = referencePath.scope.lookup(node.name); + assert.ok( + declaringScope, + '`' + node.name + '` at ' + sourcePosition(this.module, node) + + ' cannot be bound if it is not declared' + ); + + // Bindings are at the top level, so if this isn't then it's shadowing. + if (!declaringScope.isGlobal) { + return null; + } + + var declarations = declaringScope.getBindings()[node.name]; + assert.ok( + declarations && declarations.length === 1, + 'expected one declaration for `' + node.name + + '`, at ' + sourcePosition(this.module, node) + + ' but found ' + (declarations ? declarations.length : 'none') + ); + + return declarations[0]; +}; + +/** + * Generate a string representing this object to aid debugging. + * + * @return {string} + */ +ModuleBindingList.prototype.inspect = function() { + var result = '#<' + this.constructor.name; + + result += ' module=' + this.module.relativePath; + + if (this.declarations.length > 0) { + result += ' declarations=' + this.declarations.map(function(imp) { + return imp.inspect(); + }).join(', '); + } + + result += '>'; + return result; +}; + +/** + * @alias {#inspect} + */ +ModuleBindingList.prototype.toString = ModuleBindingList.prototype.inspect; + +/** + * Contains a list of declarations. + * + * @type {Array.<(Import|Export)>} + * @property declarations + */ +memo(ModuleBindingList.prototype, 'declarations', function() { + var self = this; + + return this._nodes.map(function(child) { + return self.declarationForNode(child); + }); +}); + +/** + * Contains a combined list of names for all the declarations contained in this + * list. + * + * @type {Array.} + * @property names + */ +memo(ModuleBindingList.prototype, 'names', function() { + return this.declarations.reduce(function(names, decl) { + return names.concat(decl.specifiers.map(function(specifier) { + return specifier.name; + })); + }, []); +}); + +module.exports = ModuleBindingList; diff --git a/lib/module_binding_specifier.js b/lib/module_binding_specifier.js new file mode 100644 index 0000000..56f9134 --- /dev/null +++ b/lib/module_binding_specifier.js @@ -0,0 +1,120 @@ +/* jshint node:true, undef:true, unused:true */ + +var assert = require('assert'); + +var recast = require('recast'); +var types = recast.types; +var n = types.namedTypes; + +var utils = require('./utils'); +var memo = utils.memo; +var sourcePosition = utils.sourcePosition; + +/** + */ +function ModuleBindingSpecifier(declaration, node) { + Object.defineProperties(this, { + declaration: { + value: declaration, + enumerable: false + }, + + node: { + value: node, + enumerable: false + } + }); +} + +/** + */ +memo(ModuleBindingSpecifier.prototype, 'module', function() { + return this.declaration.module; +}); + +/** + */ +memo(ModuleBindingSpecifier.prototype, 'moduleScope', function() { + return this.declaration.moduleScope; +}); + +/** + */ +memo(ModuleBindingSpecifier.prototype, 'name', function() { + return this.identifier.name; +}); + +/** + */ +memo(ModuleBindingSpecifier.prototype, 'from', function() { + return this.node.id.name; +}); + +/** + */ +memo(ModuleBindingSpecifier.prototype, 'identifier', function() { + return this.node.name || this.node.id; +}); + +/** + */ +memo(ModuleBindingSpecifier.prototype, 'exportSpecifier', function() { + var source = this.declaration.source; + if (source) { + var exports = source.exports; + return exports.findSpecifierByName(this.from); + } else { + return null; + } +}); + +memo(ModuleBindingSpecifier.prototype, 'importSpecifier', function() { + // This may be an export from this module, so find the declaration. + var localExportDeclarationInfo = this.moduleDeclaration; + + if (localExportDeclarationInfo && n.ImportDeclaration.check(localExportDeclarationInfo.declaration)) { + // It was imported then exported with two separate declarations. + var exportModule = this.module; + return exportModule.imports.findSpecifierByIdentifier(localExportDeclarationInfo.identifier); + } else { + return null; + } +}); + +memo(ModuleBindingSpecifier.prototype, 'terminalExportSpecifier', function() { + if (this.exportSpecifier) { + // This is true for both imports and exports with a source, e.g. + // `import { foo } from 'foo'` or `export { foo } from 'foo'`. + return this.exportSpecifier.terminalExportSpecifier; + } + + // This is an export from this module, so find the declaration. + var importSpecifier = this.importSpecifier; + if (importSpecifier) { + var nextExportSpecifier = importSpecifier.exportSpecifier; + assert.ok( + nextExportSpecifier, + 'expected matching export in ' + importSpecifier.declaration.source.relativePath + + ' for import of `' + importSpecifier.name + '` at ' + + sourcePosition(this.module, this.moduleDeclaration.identifier) + ); + return nextExportSpecifier.terminalExportSpecifier; + } else { + // It was declared in this module, so we are the terminal export specifier. + return this; + } +}); + +/** + */ +ModuleBindingSpecifier.prototype.inspect = function() { + return '#<' + this.constructor.name + + ' module=' + this.declaration.module.relativePath + + ' name=' + this.name + + ' from=' + this.from + + '>'; +}; + +ModuleBindingSpecifier.prototype.toString = ModuleBindingSpecifier.prototype.inspect; + +module.exports = ModuleBindingSpecifier; diff --git a/lib/parser.js b/lib/parser.js deleted file mode 100644 index e0d7a19..0000000 --- a/lib/parser.js +++ /dev/null @@ -1,96 +0,0 @@ -import { parse as esparse } from 'esprima'; - -const LITERAL = 'Literal'; - -class Parser { - constructor(script) { - this.parse(script); - } - - parse(script) { - this.imports = []; - this.exports = []; - this.directives = []; - this.exportDefault = undefined; - this.walk(esparse(script, {range: true, comment: true})); - } - - walk(node) { - if (node.type) { - var processor = this['process'+node.type]; - if (processor) { - var result = processor.call(this, node); - if (result === false) { - return; - } - } - } - - if (node.body && node.body.length > 0) { - node.body.forEach(function(child) { - child.parent = node; - this.walk(child); - }.bind(this)); - } - - // directives have to be top-level - if (node.comments && node.type === "Program") { - for (var comment of node.comments) { - if (comment.value.indexOf("transpile:") !== -1) { - this.directives.push(comment); - } - } - } - } - - processImportDeclaration(node) { - var {kind, source} = node; - - if (source.type !== LITERAL || typeof source.value !== 'string') { - throw new Error('invalid module source: '+source.value); - } - - switch (kind) { - case 'named': - this.processNamedImportDeclaration(node); - break; - - case "default": - this.processDefaultImportDeclaration(node); - break; - - // bare import (i.e. `import "foo";`) - case undefined: - this.processNamedImportDeclaration(node); - break; - - default: - throw new Error('unknown import kind: '+kind); - } - } - - processNamedImportDeclaration(node) { - this.imports.push(node); - } - - processDefaultImportDeclaration(node) { - if (node.specifiers.length !== 1) { - throw new Error('expected one specifier for default import, got '+node.specifiers.length); - } - - this.imports.push(node); - } - - processExportDeclaration(node) { - if (!node.declaration && !node.specifiers) { - throw new Error('expected declaration or specifiers after `export` keyword'); - } - this.exports.push(node); - } - - processModuleDeclaration(node) { - this.imports.push(node); - } -} - -export default Parser; diff --git a/lib/replacement.js b/lib/replacement.js new file mode 100644 index 0000000..8f2aa4e --- /dev/null +++ b/lib/replacement.js @@ -0,0 +1,58 @@ +/* jshint node:true, undef:true, unused:true */ + +/** + * Represents a replacement of a node path with zero or more nodes. + * + * @constructor + * @param {ast-types.NodePath} nodePath + * @param {Array.} nodes + */ +function Replacement(nodePath, nodes) { + this.nodePath = nodePath; + this.nodes = nodes; +} + +/** + * Performs the replacement. + */ +Replacement.prototype.replace = function() { + this.nodePath.replace.apply(this.nodePath, this.nodes); +}; + +/** + * Constructs a Replacement that, when run, will remove the node from the AST. + * + * @param {ast-types.NodePath} nodePath + * @returns {Replacement} + */ +Replacement.removes = function(nodePath) { + return new Replacement(nodePath, []); +}; + +/** + * Constructs a Replacement that, when run, will insert the given nodes after + * the one in nodePath. + * + * @param {ast-types.NodePath} nodePath + * @param {Array.} nodes + * @returns {Replacement} + */ +Replacement.adds = function(nodePath, nodes) { + return new Replacement(nodePath, [nodePath.node].concat(nodes)); +}; + +/** + * Constructs a Replacement that, when run, swaps the node in nodePath with the + * given node or nodes. + * + * @param {ast-types.NodePath} nodePath + * @param {ast-types.Node|Array.} nodes + */ +Replacement.swaps = function(nodePath, nodes) { + if ({}.toString.call(nodes) !== '[object Array]') { + nodes = [nodes]; + } + return new Replacement(nodePath, nodes); +}; + +module.exports = Replacement; diff --git a/lib/rewriter.js b/lib/rewriter.js new file mode 100644 index 0000000..8fc5717 --- /dev/null +++ b/lib/rewriter.js @@ -0,0 +1,171 @@ +/* jshint node:true, undef:true, unused:true */ + +var recast = require('recast'); +var types = recast.types; +var n = types.namedTypes; +var b = types.builders; +var astUtil = require('ast-util'); + +var Replacement = require('./replacement'); + +/** + * Replaces references to local bindings created by `mod`'s imports + * with references to the original value in the source module. + * + * @constructor + * @param {Formatter} formatter + */ +function Rewriter(formatter) { + Object.defineProperties(this, { + formatter: { + value: formatter, + enumerable: false + } + }); +} + +/** + * Rewrites references to all imported and exported bindings according to the + * rules from this rewriter's formatter. For example, this module: + * + * import { sin, cos } from './math'; + * import fib from './math/fib'; + * + * assert.equal(sin(0), 0); + * assert.equal(cos(0), 1); + * assert.equal(fib(1), 1); + * + * has its references to the imported bindings `sin`, `cos`, and `fib` + * rewritten to reference the source module: + * + * assert.equal(math$$.sin(0), 0); + * assert.equal(math$$.cos(0), 1); + * assert.equal(math$fib$$.fib(1), 1); + * + * @param {Array.} modules + */ +Rewriter.prototype.rewrite = function(modules) { + var replacements = []; + + // FIXME: This is just here to ensure that all exports know where they came + // from. We need this because after we re-write the declarations will not be + // there anymore and we'll need to ensure they're cached up front. + modules.forEach(function(mod) { + mod.exports.declarations.forEach(function(declaration) { + declaration.specifiers.forEach(function(specifier) { + return specifier.importSpecifier; + }); + }); + }); + + var self = this; + + for (var i = 0, length = modules.length; i < length; i++) { + var mod = modules[i] + types.traverse(mod.ast.program, function() { + var replacement = self.processNodePath(mod, this); + if (replacement) { + replacements.push(replacement); + } + }); + } + + replacements.forEach(function(replacement) { + if (replacement.replace) { + replacement.replace(); + } else { + var path = replacement.shift(); + path.replace.apply(path, replacement); + } + }); +}; + +/** + * Determines what, if anything, to replace the given nodePath's node with by + * delegating to this rewriter's formatter. + * + * @private + * @param {Module} mod + * @param {ast-types.NodePath} nodePath + * @return {Array.} + */ +Rewriter.prototype.processNodePath = function(mod, nodePath) { + var node = nodePath.node; + var formatter = this.formatter; + var exportReference; + + if (astUtil.isReference(nodePath)) { + exportReference = this.getExportReferenceForReference(mod, nodePath); + if (exportReference) { + return Replacement.swaps(nodePath, exportReference); + } + } else if (n.VariableDeclaration.check(node) && nodePath.scope.isGlobal) { + return formatter.processVariableDeclaration(mod, nodePath); + } else if (n.FunctionDeclaration.check(node) && n.Program.check(nodePath.parent.node)) { + return formatter.processFunctionDeclaration(mod, nodePath); + } else if (n.ExportDeclaration.check(node)) { + if (node.default) { + /** + * Default exports do not create bindings, so we can safely turn these + * into expressions that do something with the exported value. + * + * Make sure that the exported value is replaced if it is a reference + * to an imported binding. For example: + * + * import { foo } from './foo'; + * export default foo; + * + * Might become: + * + * mod$$.default = foo$$.foo; + */ + var declaration = node.declaration; + var declarationPath = nodePath.get('declaration'); + if (astUtil.isReference(declarationPath)) { + exportReference = this.getExportReferenceForReference(mod, declarationPath); + if (exportReference) { + declaration = exportReference; + } + } + return Replacement.swaps(nodePath, formatter.defaultExport(mod, declaration)); + } else { + return formatter.processExportDeclaration(mod, nodePath); + } + } else if (n.ImportDeclaration.check(node)) { + return formatter.processImportDeclaration(mod, nodePath); + } else { + return null; + } +}; + +/** + * @private + */ +Rewriter.prototype.getExportReferenceForReference = function(mod, referencePath) { + if (n.ExportSpecifier.check(referencePath.parent.node) && !referencePath.parent.node.default) { + // Do not rewrite non-default export specifiers. + return null; + } + + /** + * We need to replace references to variables that are imported or + * exported with the correct export expression. The export expression + * should be named for the original export for a variable. + * + * That is, imports must be followed to their export. If that exported + * value came from an import then repeat the process until you find a + * declaration of the exported value. + */ + var exportSpecifier = mod.exports.findSpecifierByName(referencePath.node.name); + if (exportSpecifier && exportSpecifier.declaration.source && exportSpecifier.node !== referencePath.parent.node) { + // This is a direct export from another module, e.g. `export { foo } from + // 'foo'`. There are no local bindings created by this, so there is no + // associated export for this reference and no need to rewrite it. + return null; + } + + return this.formatter.exportedReference(mod, referencePath) || + this.formatter.importedReference(mod, referencePath); +}; + +module.exports = Rewriter; diff --git a/lib/sorting.js b/lib/sorting.js new file mode 100644 index 0000000..2a5d162 --- /dev/null +++ b/lib/sorting.js @@ -0,0 +1,45 @@ +/** + * Determines the execution order of the given modules. This function resolves + * cycles by preserving the order in which the modules are visited. + * + * @param {Array.} modules + * @return {Array.} + */ +function sort(modules) { + var result = []; + var state = {}; + + modules.forEach(function(mod) { + visit(mod, result, state); + }); + + return result; +} +exports.sort = sort; + +/** + * Visits the given module, adding it to `result` after visiting all of the + * modules it imports, recursively. The `state` argument is private and maps + * module ids to the current visit state. + * + * @private + * @param {Module} mod + * @param {Array.} result + * @param {Object.} state + */ +function visit(mod, result, state) { + if (state[mod.id] === 'added') { + // already in the list, ignore it + return; + } + if (state[mod.id] === 'seen') { + // cycle found, just ignore it + return; + } + state[mod.id] = 'seen'; + mod.imports.modules.forEach(function(mod) { + visit(mod, result, state); + }); + state[mod.id] = 'added'; + result.push(mod); +} diff --git a/lib/source_modifier.js b/lib/source_modifier.js deleted file mode 100644 index 96a7d89..0000000 --- a/lib/source_modifier.js +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Borrowed this wonderful utility class from the ES6 Loader Polyfill - */ - -class SourceModifier { - constructor(source) { - this.source = source; - this.rangeOps = []; - } - - mapIndex(index) { - // apply the range operations in order to the index - for (var i = 0; i < this.rangeOps.length; i++) { - var curOp = this.rangeOps[i]; - if (curOp.start >= index) - continue; - if (curOp.end <= index) { - index += curOp.diff; - continue; - } - throw 'Source location ' + index + ' has already been transformed!'; - } - return index; - } - - replace(start, end, replacement) { - var diff = replacement.length - (end - start + 1); - - start = this.mapIndex(start); - end = this.mapIndex(end); - - this.source = this.source.substr(0, start) + replacement + this.source.substr(end + 1); - - this.rangeOps.push({ - start: start, - end: end, - diff: diff - }); - } - - getRange(start, end) { - return this.source.substr(this.mapIndex(start), this.mapIndex(end)); - } - - toString() { - return this.source; - } -} - -export default SourceModifier; diff --git a/lib/utils.js b/lib/utils.js index ddf036b..e8d2388 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,95 +1,73 @@ -var hasOwnProp = {}.hasOwnProperty; - -function isEmpty(object) { - for (var foo in object) { - if (Object.prototype.hasOwnProperty.call(object, foo)) { - return false; +/* jshint node:true, undef:true, unused:true */ + +var recast = require('recast'); +var esprima = require('esprima'); + +var proto = '__proto__'; + +function memo(object, property, getter) { + Object.defineProperty(object, property, { + get: function() { + this[property] = getter.call(this); + return this[property]; + }, + + set: function(value) { + Object.defineProperty(this, property, { + value: value, + configurable: true, + writable: false, + enumerable: false + }); } - } - return true; -} - -function uniq(array) { - var result = []; - - for (var i = 0; i < array.length; i++) { - var item = array[i]; - if (result.indexOf(item) === -1) { - result.push(item); - } - } - - return result; + }); } +exports.memo = memo; -var array = { uniq: uniq }; - -function forEach(enumerable, callback) { - if (enumerable !== null && enumerable !== undefined && typeof enumerable.forEach === 'function') { - enumerable.forEach(callback); - return; - } - - for (var key in enumerable) { - if (hasOwnProp.call(enumerable, key)) { - callback(enumerable[key], key); - } - } +function startsWith(string, substring) { + return string.lastIndexOf(substring, 0) === 0; } +exports.startsWith = startsWith; -function isWhitespace(str) { - return !str || /^\s*$/.test(str); +function endsWith(string, substring) { + var expected = string.length - substring.length; + return string.indexOf(substring, expected) === expected; } +exports.endsWith = endsWith; -function indent(lines, level, indentString=' ') { - return lines.map(function(line) { - if (!isWhitespace(line)) { - for (var i = 0; i < level; i++) { - line = indentString + line; - } - } - return line; - }); +function extend(subclass, superclass) { + subclass[proto] = superclass; + subclass.prototype = Object.create(superclass.prototype); + subclass.prototype.constructor = subclass; } - -var WHITESPACE_ONLY = /^\s*$/; -var LEADING_WHITESPACE = /^\s*/; - -function unindent(string) { - var minIndent = null; - var lines = string.split('\n'); - - for (var line of lines) { - if (!WHITESPACE_ONLY.test(line)) { - var match = line.match(LEADING_WHITESPACE); - if (match) { - if (minIndent !== null) { - minIndent = Math.min(match[0].length, minIndent); - } else { - minIndent = match[0].length; - } - } - } +exports.extend = extend; + +function sourcePosition(mod, node) { + var loc = node && node.loc; + if (loc) { + return mod.relativePath + ':' + loc.start.line + ':' + (loc.start.column + 1); + } else { + return mod.relativePath; } - - return lines.map(line => line.slice(minIndent)).join('\n'); } +exports.sourcePosition = sourcePosition; -function ltrim(string) { - return string.replace(LEADING_WHITESPACE, ''); -} +function IFFE() { + if (!IFFE.AST) { + IFFE.AST = JSON.stringify(recast.parse('(function(){})()', { esprima: esprima })); + } -var string = { indent, unindent, ltrim }; + var result = JSON.parse(IFFE.AST); + var body = result.program.body[0].expression.callee.body.body; -class Unique { - constructor(prefix) { - this.prefix = prefix; - this.index = 1; - } + Array.prototype.slice.call(arguments).forEach(function(arg) { + if (Object.prototype.toString.call(arg) === '[object Array]') { + body.push.apply(body, arg); + } else { + body.push(arg); + } + }); - next() { - return ['__', this.prefix, this.index++, '__'].join(''); - } + return result.program.body[0].expression; } - -export { isEmpty, Unique, array, forEach, string }; +exports.IFFE = IFFE; diff --git a/lib/writer.js b/lib/writer.js new file mode 100644 index 0000000..6ba2479 --- /dev/null +++ b/lib/writer.js @@ -0,0 +1,64 @@ +/* jshint node:true, undef:true, unused:true */ + +var assert = require('assert'); +var recast = require('recast'); +var fs = require('fs'); +var Path = require('path'); +var mkdirp = require('mkdirp'); + +function Writer(target) { + this.target = target; +} + +Writer.prototype.write = function(files) { + var target = this.target; + + switch (files.length) { + case 0: + throw new Error('expected at least one file to write, got zero'); + + case 1: + // We got a single file, so `target` should refer to either a file or a + // directory, but only if the file has a name. + var isDirectory = false; + try { + isDirectory = fs.statSync(target).isDirectory(); + } catch (ex) {} + + assert.ok( + !isDirectory || files[0].filename, + 'unable to determine filename for output to directory: ' + target + ); + this.writeFile( + files[0], + isDirectory ? Path.resolve(target, files[0].filename) : target + ); + break; + + default: + // We got multiple files to output, so `target` should be a directory or + // not exist (so we can create it). + var self = this; + files.forEach(function(file) { + self.writeFile(file, Path.resolve(target, file.filename)); + }); + break; + } +}; + +Writer.prototype.writeFile = function(file, filename) { + var rendered = recast.print(file); + assert.ok(filename, 'missing filename for file: ' + rendered.code); + + mkdirp.sync(Path.dirname(filename)); + fs.writeFileSync(filename, rendered.code, { encoding: 'utf8' }); + if (rendered.map) { + fs.writeFileSync( + filename + '.map', + JSON.stringify(rendered.map), + { encoding: 'utf8' } + ); + } +}; + +module.exports = Writer; diff --git a/lib/yui_compiler.js b/lib/yui_compiler.js deleted file mode 100644 index 3d413b8..0000000 --- a/lib/yui_compiler.js +++ /dev/null @@ -1,81 +0,0 @@ -import AbstractCompiler from './abstract_compiler'; -import SourceModifier from './source_modifier'; - -class YUICompiler extends AbstractCompiler { - stringify() { - var string = this.string.toString(); // string is actually a node buffer - this.source = new SourceModifier(string); - - var out = this.buildPreamble(); - - // build* mutates this.source - this.buildImports(); - this.buildExports(); - - out += `${this.indentLines(" ")} - return __exports__; -}, "@VERSION@", ${this.buildMetas()});`; - - return out; - } - - buildPreamble() { - var name = this.moduleName || "@NAME@"; // support for anonymous modules - return `YUI.add("${name}", function(Y, NAME, __imports__, __exports__) { - "use strict"; -`; - } - - buildMetas() { - return JSON.stringify({ es: true, requires: this.dependencyNames }); - } - - doModuleImport(name, dependencyName, idx) { - return `var ${name} = __imports__["${dependencyName}"];\n`; - } - - doBareImport(name) { - return ""; - } - - doDefaultImport(name, dependencyName, idx) { - if (this.options.compatFix === true) { - return `var ${name} = __imports__["${dependencyName}"]["default"] || __imports__["${dependencyName}"];\n`; - } else { - return `var ${name} = __imports__["${dependencyName}"]["default"];\n`; - } - } - - doNamedImport(name, dependencyName, alias) { - var member = (name === 'default' ? '["default"]' : '.' + name); - return `var ${alias} = __imports__["${dependencyName}"]${member};\n`; - } - - doExportSpecifier(name, reexport) { - if (reexport) { - return `__exports__.${name} = __imports__["${reexport}"].${name};\n`; - } - return `__exports__.${name} = ${name};\n`; - } - - doExportDeclaration(name) { - return `\n__exports__.${name} = ${name};`; - } - - doDefaultExport() { - return `__exports__["default"] = `; - } - - doImportSpecifiers(import_, idx) { - var dependencyName = import_.source.value; - var replacement = ""; - for (var specifier of import_.specifiers) { - var alias = specifier.name ? specifier.name.name : specifier.id.name; - replacement += this.doNamedImport(specifier.id.name, dependencyName, alias); - } - return replacement; - } - -} - -export default YUICompiler; diff --git a/package.json b/package.json index dacbbc4..08c7c29 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "es6-module-transpiler", "version": "0.4.0", - "description": "es6-module-transpiler is an experimental compiler that allows you to write your JavaScript using a subset of the current ES6 module syntax, and compile it into AMD, CommonJS, and globals styles.", + "description": "es6-module-transpiler is an experimental compiler that allows you to write your JavaScript using a subset of the current ES6 module syntax, and compile it into various formats.", "homepage": "http://square.github.com/es6-module-transpiler", "keywords": [ "es6", @@ -15,35 +15,31 @@ "compile-modules": "./bin/compile-modules" }, "directories": { - "lib": "./lib" + "lib": "./lib", + "test": "test" }, - "main": "./dist/es6-module-transpiler", + "main": "lib/index.js", "repository": { "type": "git", "url": "https://github.com/square/es6-module-transpiler.git" }, - "dependencies": { - "optimist": "~0.3.5", - "through": "~2.3.4" - }, "scripts": { - "test": "grunt build && grunt test", - "prepublish": "grunt build && grunt test" + "test": "node test/runner.js -f ${MODULE_FORMATTER:-module-variable}", + "test-module-variable": "node test/runner.js -f module-variable", + "test-export-variable": "node test/runner.js -f export-variable", + "test-commonjs": "node test/runner.js -f commonjs", + "test-all": "npm run test-module-variable && npm run test-export-variable && npm run test-commonjs" }, - "devDependencies": { - "es6ify": "~0.2.0", + "author": "Square, Inc.", + "license": "Apache 2", + "dependencies": { + "recast": "^0.5.17", "esprima": "git://github.com/thomasboyt/esprima#4be906f1abcbb", - "grunt": "~0.4.1", - "grunt-browserify": "~1.2.0", - "grunt-contrib-clean": "~0.5.0", - "grunt-contrib-concat": "~0.3.0", - "grunt-contrib-uglify": "~0.2.2", - "grunt-es6-module-transpiler": "~0.4.1", - "grunt-simple-mocha": "~0.4.0", - "matchdep": "~0.1.2", - "mocha": "~1.12.0", - "qunit-mocha-ui": "0.0.4", - "uglify-js": "~2.2.4", - "grunt-contrib-jshint": "~0.6.2" + "ast-util": "^0.0.6", + "mkdirp": "^0.5.0", + "posix-getopt": "^1.0.0" + }, + "devDependencies": { + "example-runner": "^0.1.0" } } diff --git a/tasks/es6ify.js b/tasks/es6ify.js deleted file mode 100644 index 95a90f2..0000000 --- a/tasks/es6ify.js +++ /dev/null @@ -1,21 +0,0 @@ -function transpile(grunt, file) { - var es6ify = require('es6ify'), - fs = require('fs'), - path = require('path'), - done = this.async(); - - grunt.file.mkdir(path.dirname(file.dest)); - var output = fs.createWriteStream(file.dest, 'utf8'); - - grunt.util.async.forEachSeries(file.src, function(src, next) { - var input = fs.createReadStream(src, 'utf8'); - output.once('finish', next); - input.pipe(es6ify(src)).pipe(output); - }, done); -} - -module.exports = function(grunt) { - grunt.registerMultiTask('es6ify', 'Transpiles scripts written using ES6 to ES5.', function() { - this.files.forEach(transpile.bind(this, grunt)); - }); -}; diff --git a/tasks/features.js b/tasks/features.js deleted file mode 100644 index c99fc9a..0000000 --- a/tasks/features.js +++ /dev/null @@ -1,121 +0,0 @@ -var es6Ext = '.es6.js', - typeMap = { amd: 'AMD', yui: 'YUI', cjs: 'CJS', globals: 'Globals' }, - path = require('path'); - -module.exports = function(grunt) { - function extractSourceOptions(source) { - var lines = source.split('\n'), - options = {}; - - source = grunt.util._.trim(lines.filter(function(line) { - var lineopts = optionsFromLine(line); - - if (lineopts) { - grunt.util._.extend(options, lineopts); - return false; - } - - return true; - }).join('\n')); - - return { source: source, options: options }; - } - - function optionsFromLine(line) { - var match = line.match(/^\/\*\s*transpile:\s*(.*?)\s*\*\/\s*$/); - if (!match) { return null; } - - var optsString = match[1]; - - if (optsString === 'INVALID') { - return { invalid: true }; - } - - var options = {}; - - optsString.split(/\s+/).forEach(function(pairString) { - var pair = pairString.split('='), - key = pair[0], - value = pair[1]; - - if (key === 'imports') { - var imports = {}; - value.split(',').forEach(function(importPairString) { - var importPair = importPairString.split(':'), - importPath = importPair[0], - importGlobal = importPair[1]; - imports[importPath] = importGlobal; - }); - options[key] = imports; - } else if (value === 'true') { - options[key] = true; - } else if (value === 'false') { - options[key] = false; - } else if (value === 'null') { - options[key] = null; - } else { - options[key] = value; - } - }); - - return options; - } - - grunt.registerMultiTask('features', 'Creates test files for ES6 examples.', function() { - var tmpl = grunt.file.read(this.data.template), - _ = grunt.util._, - testFiles = []; - - this.files.forEach(function(file) { - if (!_.endsWith(file.src, es6Ext)) { - grunt.log.warn("skipping non-ES6 example file: "+file.src); - return; - } - - var basename = path.basename(file.src, es6Ext), - mod = {}, - source = grunt.file.read(file.src), - lines = source.split('\n'), - options = null; - - mod.name = basename.replace(/[^a-z]/gi, ' '); - mod.basename = basename; - mod.tests = []; - - var extractedOptionsAndRemainingSource = extractSourceOptions(source); - options = extractedOptionsAndRemainingSource.options; - source = extractedOptionsAndRemainingSource.source; - - if (options.invalid) { - mod.tests.push({ - name : "does not parse", - source : source, - options : options, - invalid : true - }); - } - - ['amd', 'yui', 'cjs', 'globals'].forEach(function(type) { - var typedExt = '.'+type+'.js', - typeName = typeMap[type], - typedFile = file.src[0].replace(es6Ext, typedExt); - - if (!grunt.file.exists(typedFile)) { return; } - - var test = {}; - mod.tests.push(test); - - test.name = 'to' + typeName; - test.typeName = typeName; - test.source = source; - test.expected = _.trim(grunt.file.read(typedFile)); - test.options = options; - }); - - grunt.file.write( - file.dest.replace(es6Ext, '_test.js'), - grunt.template.process(tmpl, { data: { mod: mod } }) - ); - }); - }); -}; diff --git a/tasks/options/browserify.js b/tasks/options/browserify.js deleted file mode 100644 index dccd003..0000000 --- a/tasks/options/browserify.js +++ /dev/null @@ -1,12 +0,0 @@ -module.exports = { - dist: { - src: 'tmp/transpiled/index.js', - dest: 'tmp/es6-module-transpiler.es5.js', - options: { - transform: [function(file) { - return require('es6ify')(file); - }], - standalone: 'ModuleTranspiler' - } - } -}; diff --git a/tasks/options/clean.js b/tasks/options/clean.js deleted file mode 100644 index 031fa6a..0000000 --- a/tasks/options/clean.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = ['dist', 'bin', 'tmp', 'test/.generated']; diff --git a/tasks/options/concat.js b/tasks/options/concat.js deleted file mode 100644 index 79a0818..0000000 --- a/tasks/options/concat.js +++ /dev/null @@ -1,20 +0,0 @@ -module.exports = { - dist: { - src: [ - // FIXME: This one really ought to be require('es6ify').runtime. - // See https://github.com/thlorenz/es6ify/issues/3. - 'node_modules/es6ify/node_modules/node-traceur/src/runtime/runtime.js', - 'tmp/es6-module-transpiler.es5.js' - ], - dest: 'dist/es6-module-transpiler.js' - }, - - bin: { - options: { - banner: '#!/usr/bin/env node\n' - }, - files: { - 'bin/compile-modules': 'tmp/bin/compile-modules' - } - } -}; diff --git a/tasks/options/es6ify.js b/tasks/options/es6ify.js deleted file mode 100644 index 6872508..0000000 --- a/tasks/options/es6ify.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - cli: { - files: { - 'tmp/bin/compile-modules': 'tmp/transpiled/compile-modules.js' - } - } -}; diff --git a/tasks/options/features.js b/tasks/options/features.js deleted file mode 100644 index a119737..0000000 --- a/tasks/options/features.js +++ /dev/null @@ -1,11 +0,0 @@ -module.exports = { - all: { - template: 'test/features/test_template.js.tmpl', - files: [{ - expand: true, - cwd: 'test/features/', - src: ['**/*.es6.js'], - dest: 'test/.generated' - }] - } -}; diff --git a/tasks/options/jshint.js b/tasks/options/jshint.js deleted file mode 100644 index 9b09727..0000000 --- a/tasks/options/jshint.js +++ /dev/null @@ -1,12 +0,0 @@ -module.exports = { - all: { - src: [ - 'Gruntfile.js', - 'lib/**/*.js' - ] - }, - options: { - jshintrc: '.jshintrc', - force: true - } -}; diff --git a/tasks/options/simplemocha.js b/tasks/options/simplemocha.js deleted file mode 100644 index bf0e4a9..0000000 --- a/tasks/options/simplemocha.js +++ /dev/null @@ -1,13 +0,0 @@ -var Mocha = require('mocha'); -Mocha.interfaces['qunit-mocha-ui'] = require('qunit-mocha-ui'); - -module.exports = { - options: { - globals: ['should'], - timeout: 3000, - ignoreLeaks: false, - ui: 'qunit-mocha-ui' - }, - - all: { src: ['test/.generated/**/*.js'] } -}; diff --git a/tasks/options/transpile.js b/tasks/options/transpile.js deleted file mode 100644 index 6f0c9c1..0000000 --- a/tasks/options/transpile.js +++ /dev/null @@ -1,11 +0,0 @@ -module.exports = { - app: { - type: 'cjs', - files: [{ - expand: true, - cwd: 'lib/', - src: ['**/*.js'], - dest: 'tmp/transpiled/' - }] - } -}; diff --git a/tasks/options/uglify.js b/tasks/options/uglify.js deleted file mode 100644 index 9aa38fb..0000000 --- a/tasks/options/uglify.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - dist: { - files: { - 'dist/es6-module-transpiler.min.js': ['dist/es6-module-transpiler.js'] - } - } -}; diff --git a/test/examples/bare-import/exporter.js b/test/examples/bare-import/exporter.js new file mode 100644 index 0000000..89cf73f --- /dev/null +++ b/test/examples/bare-import/exporter.js @@ -0,0 +1,3 @@ +/* jshint esnext:true */ + +global.sideEffectyValue = 99; diff --git a/test/examples/bare-import/importer.js b/test/examples/bare-import/importer.js new file mode 100644 index 0000000..d9c51a0 --- /dev/null +++ b/test/examples/bare-import/importer.js @@ -0,0 +1,5 @@ +/* jshint esnext:true */ + +import './exporter'; + +assert.equal(global.sideEffectyValue, 99); diff --git a/test/examples/bindings/exporter.js b/test/examples/bindings/exporter.js new file mode 100644 index 0000000..a635466 --- /dev/null +++ b/test/examples/bindings/exporter.js @@ -0,0 +1,7 @@ +/* jshint esnext:true */ + +export var count = 0; + +export function incr() { + count++; +} diff --git a/test/examples/bindings/importer.js b/test/examples/bindings/importer.js new file mode 100644 index 0000000..df69785 --- /dev/null +++ b/test/examples/bindings/importer.js @@ -0,0 +1,7 @@ +/* jshint esnext:true */ + +import { count, incr } from './exporter'; + +assert.equal(count, 0); +incr(); +assert.equal(count, 1); diff --git a/test/examples/cycles-defaults/a.js b/test/examples/cycles-defaults/a.js new file mode 100644 index 0000000..4e0289b --- /dev/null +++ b/test/examples/cycles-defaults/a.js @@ -0,0 +1,5 @@ +/* jshint esnext:true */ + +import b from './b'; + +export default { a: 1, get b() { return b.b; } }; diff --git a/test/examples/cycles-defaults/b.js b/test/examples/cycles-defaults/b.js new file mode 100644 index 0000000..3ce87b6 --- /dev/null +++ b/test/examples/cycles-defaults/b.js @@ -0,0 +1,5 @@ +/* jshint esnext:true */ + +import a from './a'; + +export default { b: 2, get a() { return a.a; } }; diff --git a/test/examples/cycles-defaults/importer.js b/test/examples/cycles-defaults/importer.js new file mode 100644 index 0000000..7b5708c --- /dev/null +++ b/test/examples/cycles-defaults/importer.js @@ -0,0 +1,9 @@ +/* jshint esnext:true */ + +import a from './a'; +import b from './b'; + +assert.equal(a.a, 1); +assert.equal(a.b, 2); +assert.equal(b.a, 1); +assert.equal(b.b, 2); diff --git a/test/examples/cycles/a.js b/test/examples/cycles/a.js new file mode 100644 index 0000000..511370c --- /dev/null +++ b/test/examples/cycles/a.js @@ -0,0 +1,9 @@ +/* jshint esnext:true */ + +import { b } from './b'; + +export function getb() { + return b; +} + +export var a = 1; diff --git a/test/examples/cycles/b.js b/test/examples/cycles/b.js new file mode 100644 index 0000000..5a66f9b --- /dev/null +++ b/test/examples/cycles/b.js @@ -0,0 +1,9 @@ +/* jshint esnext:true */ + +import { a } from './a'; + +export function geta() { + return a; +} + +export var b = 2; diff --git a/test/examples/cycles/c.js b/test/examples/cycles/c.js new file mode 100644 index 0000000..c9a5044 --- /dev/null +++ b/test/examples/cycles/c.js @@ -0,0 +1,9 @@ +/* jshint esnext:true */ + +import { a, getb } from './a'; +import { b, geta } from './b'; + +assert.equal(geta(), 1); +assert.equal(a, 1); +assert.equal(getb(), 2); +assert.equal(b, 2); diff --git a/test/examples/export-and-import-reference-share-var/first.js b/test/examples/export-and-import-reference-share-var/first.js new file mode 100644 index 0000000..56ee87e --- /dev/null +++ b/test/examples/export-and-import-reference-share-var/first.js @@ -0,0 +1,4 @@ +/* jshint esnext:true */ + +export var a = 1; +assert.equal(a, 1); \ No newline at end of file diff --git a/test/examples/export-and-import-reference-share-var/second.js b/test/examples/export-and-import-reference-share-var/second.js new file mode 100644 index 0000000..f1677a0 --- /dev/null +++ b/test/examples/export-and-import-reference-share-var/second.js @@ -0,0 +1,15 @@ +/* jshint esnext:true */ + +import { a } from './first'; + +// This variable declaration is going to be altered because `b` needs to be +// re-written. We need to make sure that the `a` re-writing and the unaffected +// `c` declarator are not being clobbered by that alteration. +var a_ = a, b = 9, c = 'c'; + +assert.equal(a, 1); +assert.equal(a_, 1); +assert.equal(b, 9); +assert.equal(c, 'c'); + +export { b }; diff --git a/test/examples/export-default/exporter.js b/test/examples/export-default/exporter.js new file mode 100644 index 0000000..39138f4 --- /dev/null +++ b/test/examples/export-default/exporter.js @@ -0,0 +1,16 @@ +/* jshint esnext:true */ + +var a = 42; + +export function change() { + a++; +} + +assert.equal(a, 42); +export default a; + +// Any replacement for the `export default` above needs to happen in the same +// location. It cannot be done, say, at the end of the file. Otherwise the new +// value of `a` will be used and will be incorrect. +a = 0; +assert.equal(a, 0); diff --git a/test/examples/export-default/importer.js b/test/examples/export-default/importer.js new file mode 100644 index 0000000..e368b11 --- /dev/null +++ b/test/examples/export-default/importer.js @@ -0,0 +1,12 @@ +/* jshint esnext:true */ + +import value from './exporter'; +import { change } from './exporter'; +assert.equal(value, 42); + +change(); +assert.equal( + value, + 42, + 'default export should not be bound' +); diff --git a/test/examples/export-from/first.js b/test/examples/export-from/first.js new file mode 100644 index 0000000..c17a6e9 --- /dev/null +++ b/test/examples/export-from/first.js @@ -0,0 +1,3 @@ +/* jshint esnext:true */ + +export var a = 1; diff --git a/test/examples/export-from/second.js b/test/examples/export-from/second.js new file mode 100644 index 0000000..3d0b980 --- /dev/null +++ b/test/examples/export-from/second.js @@ -0,0 +1,7 @@ +/* jshint esnext:true */ + +export { a } from './first'; + +// This `a` reference should not be re-written because this export is not +// creating a local binding. +assert.equal(typeof a, 'undefined'); diff --git a/test/examples/export-from/third.js b/test/examples/export-from/third.js new file mode 100644 index 0000000..95314fc --- /dev/null +++ b/test/examples/export-from/third.js @@ -0,0 +1,4 @@ +/* jshint esnext:true */ + +import { a } from './second'; +assert.equal(a, 1); diff --git a/test/examples/export-function/exporter.js b/test/examples/export-function/exporter.js new file mode 100644 index 0000000..f74c2ee --- /dev/null +++ b/test/examples/export-function/exporter.js @@ -0,0 +1,6 @@ +/* jshint esnext:true */ + +export function foo() { + return 121; +} +assert.equal(foo(), 121); diff --git a/test/examples/export-function/importer.js b/test/examples/export-function/importer.js new file mode 100644 index 0000000..c2bd539 --- /dev/null +++ b/test/examples/export-function/importer.js @@ -0,0 +1,4 @@ +/* jshint esnext:true */ + +import { foo } from './exporter'; +assert.equal(foo(), 121); diff --git a/test/examples/export-list/exporter.js b/test/examples/export-list/exporter.js new file mode 100644 index 0000000..66dbba8 --- /dev/null +++ b/test/examples/export-list/exporter.js @@ -0,0 +1,10 @@ +/* jshint esnext:true */ + +var a = 1; +var b = 2; + +function incr() { + a++; b++; +} + +export { a, b, incr }; diff --git a/test/examples/export-list/importer.js b/test/examples/export-list/importer.js new file mode 100644 index 0000000..569d434 --- /dev/null +++ b/test/examples/export-list/importer.js @@ -0,0 +1,9 @@ +/* jshint esnext:true */ + +import { a, b, incr } from './exporter'; + +assert.equal(a, 1); +assert.equal(b, 2); +incr(); +assert.equal(a, 2); +assert.equal(b, 3); diff --git a/test/examples/export-var/exporter.js b/test/examples/export-var/exporter.js new file mode 100644 index 0000000..e35920b --- /dev/null +++ b/test/examples/export-var/exporter.js @@ -0,0 +1,4 @@ +/* jshint esnext:true */ + +export var a = 1; +assert.equal(a, 1); diff --git a/test/examples/export-var/importer.js b/test/examples/export-var/importer.js new file mode 100644 index 0000000..e23ff4b --- /dev/null +++ b/test/examples/export-var/importer.js @@ -0,0 +1,4 @@ +/* jshint esnext:true */ + +import { a } from './exporter'; +assert.equal(a, 1); diff --git a/test/examples/import-as/exporter.js b/test/examples/import-as/exporter.js new file mode 100644 index 0000000..6c6f1bd --- /dev/null +++ b/test/examples/import-as/exporter.js @@ -0,0 +1,5 @@ +/* jshint esnext:true */ + +export var a = 'a'; +export var b = 'b'; +export default 'DEF'; diff --git a/test/examples/import-as/importer.js b/test/examples/import-as/importer.js new file mode 100644 index 0000000..e9cc6b9 --- /dev/null +++ b/test/examples/import-as/importer.js @@ -0,0 +1,7 @@ +/* jshint esnext:true */ + +import { a as b, b as a, default as def } from './exporter'; + +assert.equal(b, 'a'); +assert.equal(a, 'b'); +assert.equal(def, 'DEF'); diff --git a/test/examples/import-chain/first.js b/test/examples/import-chain/first.js new file mode 100644 index 0000000..0cc3795 --- /dev/null +++ b/test/examples/import-chain/first.js @@ -0,0 +1,3 @@ +/* jshint esnext:true */ + +export var value = 42; diff --git a/test/examples/import-chain/second.js b/test/examples/import-chain/second.js new file mode 100644 index 0000000..e111ea3 --- /dev/null +++ b/test/examples/import-chain/second.js @@ -0,0 +1,4 @@ +/* jshint esnext:true */ + +import { value } from './first'; +export { value }; diff --git a/test/examples/import-chain/third.js b/test/examples/import-chain/third.js new file mode 100644 index 0000000..c608ea4 --- /dev/null +++ b/test/examples/import-chain/third.js @@ -0,0 +1,4 @@ +/* jshint esnext:true */ + +import { value } from './second'; +assert.equal(value, 42); diff --git a/test/examples/re-export-default-import/first.js b/test/examples/re-export-default-import/first.js new file mode 100644 index 0000000..81b78df --- /dev/null +++ b/test/examples/re-export-default-import/first.js @@ -0,0 +1,5 @@ +/* jshint esnext:true */ + +export default function hi() { + return 'hi'; +} diff --git a/test/examples/re-export-default-import/second.js b/test/examples/re-export-default-import/second.js new file mode 100644 index 0000000..d3c9eee --- /dev/null +++ b/test/examples/re-export-default-import/second.js @@ -0,0 +1,4 @@ +/* jshint esnext:true */ + +import hi from './first'; +export { hi }; diff --git a/test/examples/re-export-default-import/third.js b/test/examples/re-export-default-import/third.js new file mode 100644 index 0000000..1e0a5a4 --- /dev/null +++ b/test/examples/re-export-default-import/third.js @@ -0,0 +1,4 @@ +/* jshint esnext:true */ + +import { hi } from './second'; +assert.equal(hi(), 'hi'); diff --git a/test/features/bare_import.amd.js b/test/features/bare_import.amd.js deleted file mode 100644 index d322410..0000000 --- a/test/features/bare_import.amd.js +++ /dev/null @@ -1,7 +0,0 @@ -define( - ["foo"], - function(__dependency1__) { - "use strict"; - - }); - diff --git a/test/features/bare_import.cjs.js b/test/features/bare_import.cjs.js deleted file mode 100644 index 9650456..0000000 --- a/test/features/bare_import.cjs.js +++ /dev/null @@ -1,2 +0,0 @@ -"use strict"; -require("foo"); diff --git a/test/features/bare_import.es6.js b/test/features/bare_import.es6.js deleted file mode 100644 index c074830..0000000 --- a/test/features/bare_import.es6.js +++ /dev/null @@ -1 +0,0 @@ -import "foo"; diff --git a/test/features/bare_import.yui.js b/test/features/bare_import.yui.js deleted file mode 100644 index 9af13ba..0000000 --- a/test/features/bare_import.yui.js +++ /dev/null @@ -1,5 +0,0 @@ -YUI.add("@NAME@", function(Y, NAME, __imports__, __exports__) { - "use strict"; - - return __exports__; -}, "@VERSION@", {"es":true,"requires":["foo"]}); \ No newline at end of file diff --git a/test/features/block_comments.amd.js b/test/features/block_comments.amd.js deleted file mode 100644 index ec8fe03..0000000 --- a/test/features/block_comments.amd.js +++ /dev/null @@ -1,10 +0,0 @@ -define( - ["rsvp"], - function(__dependency1__) { - "use strict"; - var async = __dependency1__.async; - /* import { foo } from "foo"; - import { bazz } from "bazz"; - import { bar } from "bar"; - import { buzz } from "buzz"; */ - }); diff --git a/test/features/block_comments.cjs.js b/test/features/block_comments.cjs.js deleted file mode 100644 index b1bd2dc..0000000 --- a/test/features/block_comments.cjs.js +++ /dev/null @@ -1,6 +0,0 @@ -"use strict"; -var async = require("rsvp").async; -/* import { foo } from "foo"; -import { bazz } from "bazz"; -import { bar } from "bar"; -import { buzz } from "buzz"; */ diff --git a/test/features/block_comments.es6.js b/test/features/block_comments.es6.js deleted file mode 100644 index 7131041..0000000 --- a/test/features/block_comments.es6.js +++ /dev/null @@ -1,7 +0,0 @@ -/* transpile: imports=rsvp:RSVP */ - -import { async } from "rsvp"; -/* import { foo } from "foo"; -import { bazz } from "bazz"; -import { bar } from "bar"; -import { buzz } from "buzz"; */ diff --git a/test/features/block_comments.globals.js b/test/features/block_comments.globals.js deleted file mode 100644 index 96d31d3..0000000 --- a/test/features/block_comments.globals.js +++ /dev/null @@ -1,8 +0,0 @@ -(function(__dependency1__) { - "use strict"; - var async = __dependency1__.async; - /* import { foo } from "foo"; - import { bazz } from "bazz"; - import { bar } from "bar"; - import { buzz } from "buzz"; */ -})(window.RSVP); diff --git a/test/features/block_comments.yui.js b/test/features/block_comments.yui.js deleted file mode 100644 index dce33e1..0000000 --- a/test/features/block_comments.yui.js +++ /dev/null @@ -1,9 +0,0 @@ -YUI.add("@NAME@", function(Y, NAME, __imports__, __exports__) { - "use strict"; - var async = __imports__["rsvp"].async; - /* import { foo } from "foo"; - import { bazz } from "bazz"; - import { bar } from "bar"; - import { buzz } from "buzz"; */ - return __exports__; -}, "@VERSION@", {"es":true,"requires":["rsvp"]}); \ No newline at end of file diff --git a/test/features/compatfix_option.amd.js b/test/features/compatfix_option.amd.js deleted file mode 100644 index 31222d4..0000000 --- a/test/features/compatfix_option.amd.js +++ /dev/null @@ -1,6 +0,0 @@ -define( - ["jquery"], - function(__dependency1__) { - "use strict"; - var $ = __dependency1__["default"] || __dependency1__; - }); diff --git a/test/features/compatfix_option.cjs.js b/test/features/compatfix_option.cjs.js deleted file mode 100644 index 1dc0c36..0000000 --- a/test/features/compatfix_option.cjs.js +++ /dev/null @@ -1,2 +0,0 @@ -"use strict"; -var $ = require("jquery")["default"] || require("jquery"); diff --git a/test/features/compatfix_option.es6.js b/test/features/compatfix_option.es6.js deleted file mode 100644 index 96b643a..0000000 --- a/test/features/compatfix_option.es6.js +++ /dev/null @@ -1,3 +0,0 @@ -/* transpile: compatFix=true */ - -import $ from 'jquery'; diff --git a/test/features/compatfix_option.yui.js b/test/features/compatfix_option.yui.js deleted file mode 100644 index bf6383b..0000000 --- a/test/features/compatfix_option.yui.js +++ /dev/null @@ -1,5 +0,0 @@ -YUI.add("@NAME@", function(Y, NAME, __imports__, __exports__) { - "use strict"; - var $ = __imports__["jquery"]["default"] || __imports__["jquery"]; - return __exports__; -}, "@VERSION@", {"es":true,"requires":["jquery"]}); \ No newline at end of file diff --git a/test/features/export_default.amd.js b/test/features/export_default.amd.js deleted file mode 100644 index 414f3be..0000000 --- a/test/features/export_default.amd.js +++ /dev/null @@ -1,8 +0,0 @@ -define( - ["exports"], - function(__exports__) { - "use strict"; - var jQuery = function() { }; - - __exports__["default"] = jQuery; - }); diff --git a/test/features/export_default.cjs.js b/test/features/export_default.cjs.js deleted file mode 100644 index 47375a9..0000000 --- a/test/features/export_default.cjs.js +++ /dev/null @@ -1,4 +0,0 @@ -"use strict"; -var jQuery = function() { }; - -exports["default"] = jQuery; diff --git a/test/features/export_default.es6.js b/test/features/export_default.es6.js deleted file mode 100644 index ed4711e..0000000 --- a/test/features/export_default.es6.js +++ /dev/null @@ -1,3 +0,0 @@ -var jQuery = function() { }; - -export default jQuery; diff --git a/test/features/export_default.globals.js b/test/features/export_default.globals.js deleted file mode 100644 index 86d5e71..0000000 --- a/test/features/export_default.globals.js +++ /dev/null @@ -1,6 +0,0 @@ -(function(__exports__) { - "use strict"; - var jQuery = function() { }; - - __exports__.jQuery = jQuery; -})(window); diff --git a/test/features/export_default.yui.js b/test/features/export_default.yui.js deleted file mode 100644 index 419bc24..0000000 --- a/test/features/export_default.yui.js +++ /dev/null @@ -1,7 +0,0 @@ -YUI.add("@NAME@", function(Y, NAME, __imports__, __exports__) { - "use strict"; - var jQuery = function() { }; - - __exports__["default"] = jQuery; - return __exports__; -}, "@VERSION@", {"es":true,"requires":[]}); \ No newline at end of file diff --git a/test/features/export_from_module.amd.js b/test/features/export_from_module.amd.js deleted file mode 100644 index 6fa7f70..0000000 --- a/test/features/export_from_module.amd.js +++ /dev/null @@ -1,7 +0,0 @@ -define( - ["path","exports"], - function(__dependency1__, __exports__) { - "use strict"; - __exports__.join = __dependency1__.join; - __exports__.extname = __dependency1__.extname; - }); diff --git a/test/features/export_from_module.cjs.js b/test/features/export_from_module.cjs.js deleted file mode 100644 index 2d75453..0000000 --- a/test/features/export_from_module.cjs.js +++ /dev/null @@ -1,3 +0,0 @@ -"use strict"; -exports.join = require("path").join; -exports.extname = require("path").extname; diff --git a/test/features/export_from_module.es6.js b/test/features/export_from_module.es6.js deleted file mode 100644 index 3f35d32..0000000 --- a/test/features/export_from_module.es6.js +++ /dev/null @@ -1,3 +0,0 @@ -/* transpile: imports=path:path */ - -export { join, extname } from "path"; diff --git a/test/features/export_from_module.globals.js b/test/features/export_from_module.globals.js deleted file mode 100644 index b916c0f..0000000 --- a/test/features/export_from_module.globals.js +++ /dev/null @@ -1,5 +0,0 @@ -(function(__exports__, __dependency1__) { - "use strict"; - __exports__.join = __dependency1__.join; - __exports__.extname = __dependency1__.extname; -})(window, window.path); diff --git a/test/features/export_from_module.yui.js b/test/features/export_from_module.yui.js deleted file mode 100644 index 8826bfb..0000000 --- a/test/features/export_from_module.yui.js +++ /dev/null @@ -1,6 +0,0 @@ -YUI.add("@NAME@", function(Y, NAME, __imports__, __exports__) { - "use strict"; - __exports__.join = __imports__["path"].join; - __exports__.extname = __imports__["path"].extname; - return __exports__; -}, "@VERSION@", {"es":true,"requires":["path"]}); \ No newline at end of file diff --git a/test/features/export_function.amd.js b/test/features/export_function.amd.js deleted file mode 100644 index 6f4ce8e..0000000 --- a/test/features/export_function.amd.js +++ /dev/null @@ -1,7 +0,0 @@ -define( - ["exports"], - function(__exports__) { - "use strict"; - function jQuery() { }; - __exports__.jQuery = jQuery; - }); diff --git a/test/features/export_function.cjs.js b/test/features/export_function.cjs.js deleted file mode 100644 index 260399e..0000000 --- a/test/features/export_function.cjs.js +++ /dev/null @@ -1,3 +0,0 @@ -"use strict"; -function jQuery() { }; -exports.jQuery = jQuery; diff --git a/test/features/export_function.es6.js b/test/features/export_function.es6.js deleted file mode 100644 index 43e4a40..0000000 --- a/test/features/export_function.es6.js +++ /dev/null @@ -1 +0,0 @@ -export function jQuery() { }; diff --git a/test/features/export_function.globals.js b/test/features/export_function.globals.js deleted file mode 100644 index 0402fdf..0000000 --- a/test/features/export_function.globals.js +++ /dev/null @@ -1,5 +0,0 @@ -(function(__exports__) { - "use strict"; - function jQuery() { }; - __exports__.jQuery = jQuery; -})(window); diff --git a/test/features/export_function.yui.js b/test/features/export_function.yui.js deleted file mode 100644 index 1fbf3c2..0000000 --- a/test/features/export_function.yui.js +++ /dev/null @@ -1,6 +0,0 @@ -YUI.add("@NAME@", function(Y, NAME, __imports__, __exports__) { - "use strict"; - function jQuery() { }; - __exports__.jQuery = jQuery; - return __exports__; -}, "@VERSION@", {"es":true,"requires":[]}); \ No newline at end of file diff --git a/test/features/export_identifier.amd.js b/test/features/export_identifier.amd.js deleted file mode 100644 index eec06f9..0000000 --- a/test/features/export_identifier.amd.js +++ /dev/null @@ -1,8 +0,0 @@ -define( - ["exports"], - function(__exports__) { - "use strict"; - var jQuery = function() { }; - - __exports__.jQuery = jQuery; - }); diff --git a/test/features/export_identifier.cjs.js b/test/features/export_identifier.cjs.js deleted file mode 100644 index 009936f..0000000 --- a/test/features/export_identifier.cjs.js +++ /dev/null @@ -1,4 +0,0 @@ -"use strict"; -var jQuery = function() { }; - -exports.jQuery = jQuery; diff --git a/test/features/export_identifier.es6.js b/test/features/export_identifier.es6.js deleted file mode 100644 index 4f883c6..0000000 --- a/test/features/export_identifier.es6.js +++ /dev/null @@ -1,3 +0,0 @@ -var jQuery = function() { }; - -export { jQuery }; diff --git a/test/features/export_identifier.globals.js b/test/features/export_identifier.globals.js deleted file mode 100644 index 86d5e71..0000000 --- a/test/features/export_identifier.globals.js +++ /dev/null @@ -1,6 +0,0 @@ -(function(__exports__) { - "use strict"; - var jQuery = function() { }; - - __exports__.jQuery = jQuery; -})(window); diff --git a/test/features/export_identifier.yui.js b/test/features/export_identifier.yui.js deleted file mode 100644 index 468ef17..0000000 --- a/test/features/export_identifier.yui.js +++ /dev/null @@ -1,7 +0,0 @@ -YUI.add("@NAME@", function(Y, NAME, __imports__, __exports__) { - "use strict"; - var jQuery = function() { }; - - __exports__.jQuery = jQuery; - return __exports__; -}, "@VERSION@", {"es":true,"requires":[]}); \ No newline at end of file diff --git a/test/features/export_specifier_set.amd.js b/test/features/export_specifier_set.amd.js deleted file mode 100644 index 9d4e6d5..0000000 --- a/test/features/export_specifier_set.amd.js +++ /dev/null @@ -1,10 +0,0 @@ -define( - ["exports"], - function(__exports__) { - "use strict"; - var get = function() { }; - var set = function() { }; - - __exports__.get = get; - __exports__.set = set; - }); diff --git a/test/features/export_specifier_set.cjs.js b/test/features/export_specifier_set.cjs.js deleted file mode 100644 index 827c922..0000000 --- a/test/features/export_specifier_set.cjs.js +++ /dev/null @@ -1,6 +0,0 @@ -"use strict"; -var get = function() { }; -var set = function() { }; - -exports.get = get; -exports.set = set; diff --git a/test/features/export_specifier_set.es6.js b/test/features/export_specifier_set.es6.js deleted file mode 100644 index 27886a3..0000000 --- a/test/features/export_specifier_set.es6.js +++ /dev/null @@ -1,6 +0,0 @@ -/* transpile: into=Ember */ - -var get = function() { }; -var set = function() { }; - -export { get, set }; diff --git a/test/features/export_specifier_set.globals.js b/test/features/export_specifier_set.globals.js deleted file mode 100644 index 5f896f9..0000000 --- a/test/features/export_specifier_set.globals.js +++ /dev/null @@ -1,8 +0,0 @@ -(function(__exports__) { - "use strict"; - var get = function() { }; - var set = function() { }; - - __exports__.get = get; - __exports__.set = set; -})(window.Ember = {}); diff --git a/test/features/export_specifier_set.yui.js b/test/features/export_specifier_set.yui.js deleted file mode 100644 index 7a7431c..0000000 --- a/test/features/export_specifier_set.yui.js +++ /dev/null @@ -1,9 +0,0 @@ -YUI.add("@NAME@", function(Y, NAME, __imports__, __exports__) { - "use strict"; - var get = function() { }; - var set = function() { }; - - __exports__.get = get; - __exports__.set = set; - return __exports__; -}, "@VERSION@", {"es":true,"requires":[]}); \ No newline at end of file diff --git a/test/features/export_var.amd.js b/test/features/export_var.amd.js deleted file mode 100644 index 6a18386..0000000 --- a/test/features/export_var.amd.js +++ /dev/null @@ -1,7 +0,0 @@ -define( - ["exports"], - function(__exports__) { - "use strict"; - var jQuery = function() { }; - __exports__.jQuery = jQuery; - }); diff --git a/test/features/export_var.cjs.js b/test/features/export_var.cjs.js deleted file mode 100644 index d69bb54..0000000 --- a/test/features/export_var.cjs.js +++ /dev/null @@ -1,3 +0,0 @@ -"use strict"; -var jQuery = function() { }; -exports.jQuery = jQuery; diff --git a/test/features/export_var.es6.js b/test/features/export_var.es6.js deleted file mode 100644 index 7084044..0000000 --- a/test/features/export_var.es6.js +++ /dev/null @@ -1 +0,0 @@ -export var jQuery = function() { }; diff --git a/test/features/export_var.globals.js b/test/features/export_var.globals.js deleted file mode 100644 index fb80c19..0000000 --- a/test/features/export_var.globals.js +++ /dev/null @@ -1,5 +0,0 @@ -(function(__exports__) { - "use strict"; - var jQuery = function() { }; - __exports__.jQuery = jQuery; -})(window); diff --git a/test/features/export_var.yui.js b/test/features/export_var.yui.js deleted file mode 100644 index 0cf4af4..0000000 --- a/test/features/export_var.yui.js +++ /dev/null @@ -1,6 +0,0 @@ -YUI.add("@NAME@", function(Y, NAME, __imports__, __exports__) { - "use strict"; - var jQuery = function() { }; - __exports__.jQuery = jQuery; - return __exports__; -}, "@VERSION@", {"es":true,"requires":[]}); \ No newline at end of file diff --git a/test/features/import_default.amd.js b/test/features/import_default.amd.js deleted file mode 100644 index 0975d6e..0000000 --- a/test/features/import_default.amd.js +++ /dev/null @@ -1,6 +0,0 @@ -define( - ["rsvp"], - function(__dependency1__) { - "use strict"; - var RSVP = __dependency1__["default"]; - }); diff --git a/test/features/import_default.cjs.js b/test/features/import_default.cjs.js deleted file mode 100644 index e1dc093..0000000 --- a/test/features/import_default.cjs.js +++ /dev/null @@ -1,2 +0,0 @@ -"use strict"; -var RSVP = require("rsvp")["default"]; diff --git a/test/features/import_default.es6.js b/test/features/import_default.es6.js deleted file mode 100644 index a070ae4..0000000 --- a/test/features/import_default.es6.js +++ /dev/null @@ -1,3 +0,0 @@ -/* transpile: imports=rsvp:RSVP */ - -import RSVP from 'rsvp'; diff --git a/test/features/import_default.globals.js b/test/features/import_default.globals.js deleted file mode 100644 index ca525b9..0000000 --- a/test/features/import_default.globals.js +++ /dev/null @@ -1,4 +0,0 @@ -(function(__dependency1__) { - "use strict"; - var RSVP = __dependency1__; -})(window.RSVP); diff --git a/test/features/import_default.yui.js b/test/features/import_default.yui.js deleted file mode 100644 index f1f3e96..0000000 --- a/test/features/import_default.yui.js +++ /dev/null @@ -1,5 +0,0 @@ -YUI.add("@NAME@", function(Y, NAME, __imports__, __exports__) { - "use strict"; - var RSVP = __imports__["rsvp"]["default"]; - return __exports__; -}, "@VERSION@", {"es":true,"requires":["rsvp"]}); \ No newline at end of file diff --git a/test/features/import_default_as.amd.js b/test/features/import_default_as.amd.js deleted file mode 100644 index 46f2048..0000000 --- a/test/features/import_default_as.amd.js +++ /dev/null @@ -1,6 +0,0 @@ -define( - ["ember"], - function(__dependency1__) { - "use strict"; - var Ember = __dependency1__.default; - }); diff --git a/test/features/import_default_as.es6.js b/test/features/import_default_as.es6.js deleted file mode 100644 index 89fb1fe..0000000 --- a/test/features/import_default_as.es6.js +++ /dev/null @@ -1 +0,0 @@ -import { default as Ember } from "ember"; diff --git a/test/features/import_default_as.yui.js b/test/features/import_default_as.yui.js deleted file mode 100644 index 4f032f0..0000000 --- a/test/features/import_default_as.yui.js +++ /dev/null @@ -1,5 +0,0 @@ -YUI.add("@NAME@", function(Y, NAME, __imports__, __exports__) { - "use strict"; - var Ember = __imports__["ember"]["default"]; - return __exports__; -}, "@VERSION@", {"es":true,"requires":["ember"]}); \ No newline at end of file diff --git a/test/features/import_module.amd.js b/test/features/import_module.amd.js deleted file mode 100644 index 74eaad8..0000000 --- a/test/features/import_module.amd.js +++ /dev/null @@ -1,7 +0,0 @@ -define( - ["foo","./foo/bar"], - function(__dependency1__, __dependency2__) { - "use strict"; - var foo = __dependency1__; - var bar = __dependency2__; - }); diff --git a/test/features/import_module.cjs.js b/test/features/import_module.cjs.js deleted file mode 100644 index a650542..0000000 --- a/test/features/import_module.cjs.js +++ /dev/null @@ -1,26 +0,0 @@ -"use strict"; -function __es6_transpiler_warn__(warning) { - if (typeof console === 'undefined') { - } else if (typeof console.warn === "function") { - console.warn(warning); - } else if (typeof console.log === "function") { - console.log(warning); - } -} -function __es6_transpiler_build_module_object__(name, imported) { - var moduleInstanceObject = Object.create ? Object.create(null) : {}; - if (typeof imported === "function") { - __es6_transpiler_warn__("imported module '"+name+"' exported a function - this may not work as expected"); - } - for (var key in imported) { - if (Object.prototype.hasOwnProperty.call(imported, key)) { - moduleInstanceObject[key] = imported[key]; - } - } - if (Object.freeze) { - Object.freeze(moduleInstanceObject); - } - return moduleInstanceObject; -} -var foo = __es6_transpiler_build_module_object__("foo", require("foo")); -var bar = __es6_transpiler_build_module_object__("bar", require("./foo/bar")); diff --git a/test/features/import_module.es6.js b/test/features/import_module.es6.js deleted file mode 100644 index 34afc1f..0000000 --- a/test/features/import_module.es6.js +++ /dev/null @@ -1,2 +0,0 @@ -module foo from "foo"; -module bar from "./foo/bar"; diff --git a/test/features/import_module.yui.js b/test/features/import_module.yui.js deleted file mode 100644 index 9905603..0000000 --- a/test/features/import_module.yui.js +++ /dev/null @@ -1,6 +0,0 @@ -YUI.add("@NAME@", function(Y, NAME, __imports__, __exports__) { - "use strict"; - var foo = __imports__["foo"]; - var bar = __imports__["./foo/bar"]; - return __exports__; -}, "@VERSION@", {"es":true,"requires":["foo","./foo/bar"]}); diff --git a/test/features/import_specifier.amd.js b/test/features/import_specifier.amd.js deleted file mode 100644 index 4b5ce97..0000000 --- a/test/features/import_specifier.amd.js +++ /dev/null @@ -1,6 +0,0 @@ -define( - ["foo"], - function(__dependency1__) { - "use strict"; - - }); diff --git a/test/features/import_specifier.cjs.js b/test/features/import_specifier.cjs.js deleted file mode 100644 index 9650456..0000000 --- a/test/features/import_specifier.cjs.js +++ /dev/null @@ -1,2 +0,0 @@ -"use strict"; -require("foo"); diff --git a/test/features/import_specifier.es6.js b/test/features/import_specifier.es6.js deleted file mode 100644 index c074830..0000000 --- a/test/features/import_specifier.es6.js +++ /dev/null @@ -1 +0,0 @@ -import "foo"; diff --git a/test/features/import_specifier.yui.js b/test/features/import_specifier.yui.js deleted file mode 100644 index 9af13ba..0000000 --- a/test/features/import_specifier.yui.js +++ /dev/null @@ -1,5 +0,0 @@ -YUI.add("@NAME@", function(Y, NAME, __imports__, __exports__) { - "use strict"; - - return __exports__; -}, "@VERSION@", {"es":true,"requires":["foo"]}); \ No newline at end of file diff --git a/test/features/import_specifier_set.amd.js b/test/features/import_specifier_set.amd.js deleted file mode 100644 index edf23b4..0000000 --- a/test/features/import_specifier_set.amd.js +++ /dev/null @@ -1,8 +0,0 @@ -define( - ["ember","rsvp"], - function(__dependency1__, __dependency2__) { - "use strict"; - var get = __dependency1__.get; - var set = __dependency1__.set; - var makeDeferred = __dependency2__.defer; - }); diff --git a/test/features/import_specifier_set.cjs.js b/test/features/import_specifier_set.cjs.js deleted file mode 100644 index 2efbce9..0000000 --- a/test/features/import_specifier_set.cjs.js +++ /dev/null @@ -1,4 +0,0 @@ -"use strict"; -var get = require("ember").get; -var set = require("ember").set; -var makeDeferred = require("rsvp").defer; diff --git a/test/features/import_specifier_set.es6.js b/test/features/import_specifier_set.es6.js deleted file mode 100644 index 35e971d..0000000 --- a/test/features/import_specifier_set.es6.js +++ /dev/null @@ -1,4 +0,0 @@ -/* transpile: imports=ember:Ember,rsvp:RSVP */ - -import { get, set } from 'ember'; -import { defer as makeDeferred } from 'rsvp'; diff --git a/test/features/import_specifier_set.globals.js b/test/features/import_specifier_set.globals.js deleted file mode 100644 index 5fff9ae..0000000 --- a/test/features/import_specifier_set.globals.js +++ /dev/null @@ -1,6 +0,0 @@ -(function(__dependency1__, __dependency2__) { - "use strict"; - var get = __dependency1__.get; - var set = __dependency1__.set; - var makeDeferred = __dependency2__.defer; -})(window.Ember, window.RSVP); diff --git a/test/features/import_specifier_set.yui.js b/test/features/import_specifier_set.yui.js deleted file mode 100644 index 06f7518..0000000 --- a/test/features/import_specifier_set.yui.js +++ /dev/null @@ -1,7 +0,0 @@ -YUI.add("@NAME@", function(Y, NAME, __imports__, __exports__) { - "use strict"; - var get = __imports__["ember"].get; - var set = __imports__["ember"].set; - var makeDeferred = __imports__["rsvp"].defer; - return __exports__; -}, "@VERSION@", {"es":true,"requires":["ember","rsvp"]}); \ No newline at end of file diff --git a/test/features/multi_line_declaration.amd.js b/test/features/multi_line_declaration.amd.js deleted file mode 100644 index 17fe566..0000000 --- a/test/features/multi_line_declaration.amd.js +++ /dev/null @@ -1,8 +0,0 @@ -define( - ["foo","exports"], - function(__dependency1__, __exports__) { - "use strict"; - __exports__["default"] = 1 + 2; - - var foo = __dependency1__.foo; - }); diff --git a/test/features/multi_line_declaration.cjs.js b/test/features/multi_line_declaration.cjs.js deleted file mode 100644 index 95540dd..0000000 --- a/test/features/multi_line_declaration.cjs.js +++ /dev/null @@ -1,4 +0,0 @@ -"use strict"; -exports["default"] = 1 + 2; - -var foo = require("foo").foo; diff --git a/test/features/multi_line_declaration.es6.js b/test/features/multi_line_declaration.es6.js deleted file mode 100644 index 8cd5ae2..0000000 --- a/test/features/multi_line_declaration.es6.js +++ /dev/null @@ -1,6 +0,0 @@ -export -default -1 + 2; - -import -{ foo } from "foo"; diff --git a/test/features/multi_line_declaration.yui.js b/test/features/multi_line_declaration.yui.js deleted file mode 100644 index 2cab6cc..0000000 --- a/test/features/multi_line_declaration.yui.js +++ /dev/null @@ -1,7 +0,0 @@ -YUI.add("@NAME@", function(Y, NAME, __imports__, __exports__) { - "use strict"; - __exports__["default"] = 1 + 2; - - var foo = __imports__["foo"].foo; - return __exports__; -}, "@VERSION@", {"es":true,"requires":["foo"]}); \ No newline at end of file diff --git a/test/features/multiple_import_from_same_path.amd.js b/test/features/multiple_import_from_same_path.amd.js deleted file mode 100644 index 62140e9..0000000 --- a/test/features/multiple_import_from_same_path.amd.js +++ /dev/null @@ -1,7 +0,0 @@ -define( - ["utils"], - function(__dependency1__) { - "use strict"; - var uniq = __dependency1__.uniq; - var forEach = __dependency1__.forEach; - }); diff --git a/test/features/multiple_import_from_same_path.cjs.js b/test/features/multiple_import_from_same_path.cjs.js deleted file mode 100644 index 46717ef..0000000 --- a/test/features/multiple_import_from_same_path.cjs.js +++ /dev/null @@ -1,3 +0,0 @@ -"use strict"; -var uniq = require("utils").uniq; -var forEach = require("utils").forEach; diff --git a/test/features/multiple_import_from_same_path.es6.js b/test/features/multiple_import_from_same_path.es6.js deleted file mode 100644 index 4c44b79..0000000 --- a/test/features/multiple_import_from_same_path.es6.js +++ /dev/null @@ -1,4 +0,0 @@ -/* transpile: imports=utils:utils */ - -import { uniq } from 'utils'; -import { forEach } from 'utils'; diff --git a/test/features/multiple_import_from_same_path.globals.js b/test/features/multiple_import_from_same_path.globals.js deleted file mode 100644 index ecc2305..0000000 --- a/test/features/multiple_import_from_same_path.globals.js +++ /dev/null @@ -1,5 +0,0 @@ -(function(__dependency1__) { - "use strict"; - var uniq = __dependency1__.uniq; - var forEach = __dependency1__.forEach; -})(window.utils); diff --git a/test/features/multiple_import_from_same_path.yui.js b/test/features/multiple_import_from_same_path.yui.js deleted file mode 100644 index b0d92cd..0000000 --- a/test/features/multiple_import_from_same_path.yui.js +++ /dev/null @@ -1,6 +0,0 @@ -YUI.add("@NAME@", function(Y, NAME, __imports__, __exports__) { - "use strict"; - var uniq = __imports__["utils"].uniq; - var forEach = __imports__["utils"].forEach; - return __exports__; -}, "@VERSION@", {"es":true,"requires":["utils"]}); \ No newline at end of file diff --git a/test/features/new_default.amd.js b/test/features/new_default.amd.js deleted file mode 100644 index f350154..0000000 --- a/test/features/new_default.amd.js +++ /dev/null @@ -1,13 +0,0 @@ -define( - ["foo","bar","exports"], - function(__dependency1__, __dependency2__, __exports__) { - "use strict"; - var foo = __dependency1__; - var bar = __dependency2__["default"]; - - var baz = "baz"; - var qux = "qux"; - - __exports__["default"] = baz; - __exports__.qux = qux; - }); diff --git a/test/features/new_default.cjs.js b/test/features/new_default.cjs.js deleted file mode 100644 index 4bb4c9d..0000000 --- a/test/features/new_default.cjs.js +++ /dev/null @@ -1,32 +0,0 @@ -"use strict"; -function __es6_transpiler_warn__(warning) { - if (typeof console === 'undefined') { - } else if (typeof console.warn === "function") { - console.warn(warning); - } else if (typeof console.log === "function") { - console.log(warning); - } -} -function __es6_transpiler_build_module_object__(name, imported) { - var moduleInstanceObject = Object.create ? Object.create(null) : {}; - if (typeof imported === "function") { - __es6_transpiler_warn__("imported module '"+name+"' exported a function - this may not work as expected"); - } - for (var key in imported) { - if (Object.prototype.hasOwnProperty.call(imported, key)) { - moduleInstanceObject[key] = imported[key]; - } - } - if (Object.freeze) { - Object.freeze(moduleInstanceObject); - } - return moduleInstanceObject; -} -var foo = __es6_transpiler_build_module_object__("foo", require("foo")); -var bar = require("bar")["default"]; - -var baz = "baz"; -var qux = "qux"; - -exports["default"] = baz; -exports.qux = qux; diff --git a/test/features/new_default.es6.js b/test/features/new_default.es6.js deleted file mode 100644 index c07c683..0000000 --- a/test/features/new_default.es6.js +++ /dev/null @@ -1,8 +0,0 @@ -module foo from "foo"; -import bar from "bar"; - -var baz = "baz"; -var qux = "qux"; - -export default baz; -export { qux }; diff --git a/test/features/new_default.yui.js b/test/features/new_default.yui.js deleted file mode 100644 index 5a1cebc..0000000 --- a/test/features/new_default.yui.js +++ /dev/null @@ -1,12 +0,0 @@ -YUI.add("@NAME@", function(Y, NAME, __imports__, __exports__) { - "use strict"; - var foo = __imports__["foo"]; - var bar = __imports__["bar"]["default"]; - - var baz = "baz"; - var qux = "qux"; - - __exports__["default"] = baz; - __exports__.qux = qux; - return __exports__; -}, "@VERSION@", {"es":true,"requires":["foo","bar"]}); \ No newline at end of file diff --git a/test/features/semicolons_optional.cjs.js b/test/features/semicolons_optional.cjs.js deleted file mode 100644 index 34ef3dc..0000000 --- a/test/features/semicolons_optional.cjs.js +++ /dev/null @@ -1,2 +0,0 @@ -"use strict"; -var ajax = require("jquery").ajax;exports.defer = require("rsvp").defer; diff --git a/test/features/semicolons_optional.es6.js b/test/features/semicolons_optional.es6.js deleted file mode 100644 index 171eb42..0000000 --- a/test/features/semicolons_optional.es6.js +++ /dev/null @@ -1,2 +0,0 @@ -import { ajax } from 'jquery' -export { defer } from 'rsvp' diff --git a/test/features/semicolons_optional.yui.js b/test/features/semicolons_optional.yui.js deleted file mode 100644 index 4529380..0000000 --- a/test/features/semicolons_optional.yui.js +++ /dev/null @@ -1,5 +0,0 @@ -YUI.add("@NAME@", function(Y, NAME, __imports__, __exports__) { - "use strict"; - var ajax = __imports__["jquery"].ajax;__exports__.defer = __imports__["rsvp"].defer; - return __exports__; -}, "@VERSION@", {"es":true,"requires":["jquery","rsvp"]}); \ No newline at end of file diff --git a/test/features/test_template.js.tmpl b/test/features/test_template.js.tmpl deleted file mode 100644 index ebb257f..0000000 --- a/test/features/test_template.js.tmpl +++ /dev/null @@ -1,30 +0,0 @@ -<% - function compiler(test) { - return "var compiler = new Compiler(" + - JSON.stringify(test.source) + - ", null, " + - JSON.stringify(test.options) + - ");"; - } -%> - -var Compiler = require("../../dist/es6-module-transpiler").Compiler; - -suite("<%= mod.name %>"); - -<% _.each(mod.tests, function(test) { %> -test("<%= test.name %>", function() { - <% if (test.invalid) { %> -try { - <%= compiler(test) %> - compiler.toAMD(); - ok(false, "should have raised an error"); -} catch (e) { - ok(e, e ? e.message : "expects an error"); -} - <% } else { %> - <%= compiler(test) %> -equal(compiler.to<%= test.typeName %>(), <%= JSON.stringify(test.expected) %>); - <% } %> -}); -<% }); %> diff --git a/test/runner.js b/test/runner.js new file mode 100644 index 0000000..c66f5c5 --- /dev/null +++ b/test/runner.js @@ -0,0 +1,218 @@ +/* jshint node:true, undef:true, unused:true */ + +Error.stackTraceLimit = 50; + +var fs = require('fs'); +var Path = require('path'); +var vm = require('vm'); +var assert = require('assert'); + +var modules = require('../lib'); +var utils = require('../lib/utils'); +var endsWith = utils.endsWith; + +var examples = Path.join(__dirname, 'examples'); + +var paths = []; +var formatters = require('../lib/formatters'); +var formatterNames = Object.keys(formatters).filter(function(formatter) { + return formatter !== 'DEFAULT'; +}); +var formatter = formatters.DEFAULT; + +var getopt = require('posix-getopt'); +var parser = new getopt.BasicParser('h(help)f:(format)', process.argv); +var option; + +while ((option = parser.getopt()) !== undefined) { + if (option.error) { + usage(); + process.exit(1); + } + + switch (option.option) { + case 'f': + formatter = option.optarg; + if (formatterNames.indexOf(formatter) < 0) { + usage(); + process.exit(1); + } + break; + + case 'h': + usage(); + process.exit(0); + break; + } +} + +paths.push.apply(paths, process.argv.slice(parser.optind())); + +if (paths.length === 0) { + paths = fs.readdirSync(examples).map(function(example) { + return Path.join(examples, example); + }); +} else { + var cwd = process.cwd(); + paths = paths.map(function(example) { + return Path.resolve(cwd, example); + }); +} + +var results = Path.join(__dirname, 'results'); +if (fs.existsSync(results)) { + rmrf(results); +} +fs.mkdirSync(results); +runTests(paths); + +function runTests(paths) { + var passed = 0, failed = 0; + paths.forEach(function(path) { + if (runTestDir(path)) { + passed++; + } else { + failed++; + } + }); + + console.log(); + console.log('%d passed, %s failed.', passed, failed); + process.exit( + (passed + failed === 0) ? 1 : // no tests, fail + failed === 0 ? 0 : // no failed, pass + 1); // some failed, fail +} + +function runTestDir(testDir) { + var passed = false; + var testName = Path.basename(testDir); + + var options = { + resolvers: [new modules.FileResolver([testDir])], + formatter: formatters[formatter] + }; + var container = modules.makeContainer(options); + + try { + fs.readdirSync(testDir).forEach(function(child) { + container.getModule(child); + }); + + var resultPath = Path.join(results, testName + '.js'); + container.write(resultPath); + + var testAssert = wrappedAssert(); + if (fs.statSync(resultPath).isDirectory()) { + fs.readdirSync(resultPath).forEach(function(child) { + requireTestFile('./' + child, resultPath, testAssert); + }); + } else { + requireTestFile(resultPath, process.cwd(), testAssert); + } + + assert.ok( + testAssert.count > 0, + 'expected at least one assertion' + ); + + passed = true; + printSuccess(testName); + } catch (ex) { + printFailure(testName, ex); + console.log(); + } + + return passed; +} + +// TODO: Just use the real node require system with proxyquire? +var testFileCache; +var testFileGlobal; +function requireTestFile(path, relativeTo, assert) { + if (path[0] === '.') { + path = Path.resolve(relativeTo, path); + } + + if (!testFileCache) { testFileCache = {}; } + + if (path in testFileCache) { + return testFileCache[path]; + } else if (!fs.existsSync(path) && !endsWith(path, '.js')) { + return requireTestFile(path + '.js'); + } + + var code = fs.readFileSync(path); + var mod = {exports: {}}; + testFileCache[path] = mod.exports; + + if (!testFileGlobal) { testFileGlobal = {}; } + + vm.runInNewContext(code, { + assert: assert, + global: testFileGlobal, + + module: mod, + exports: mod.exports, + require: function(requiredPath) { + return requireTestFile(requiredPath, Path.dirname(path), assert); + } + }, path); + + testFileCache[path] = mod.exports; + return mod.exports; +} + +function wrappedAssert() { + var result = {count: 0}; + + Object.getOwnPropertyNames(assert).forEach(function(property) { + result[property] = function() { + result.count++; + return assert[property].apply(assert, arguments); + }; + }); + + return result; +} + +function rmrf(path) { + var stat = fs.statSync(path); + if (stat.isDirectory()) { + fs.readdirSync(path).forEach(function(child) { + rmrf(Path.join(path, child)); + }); + fs.rmdirSync(path); + } else if (stat.isFile()) { + fs.unlinkSync(path); + } +} + +/** + * Prints a line to stdout for the given test indicating that it passed. + * + * @param {string} testName + */ +function printSuccess(testName) { + console.log('\x1b[32m✓ \x1b[0m' + testName); +} + +/** + * Prints a line to stdout for the given test indicating that it failed. In + * addition, prints any additional information indented one level. + * + * @param {string} testName + * @param {Error} error + */ +function printFailure(testName, error) { + console.log('\x1b[31m✘ ' + testName + '\x1b[0m'); + console.log(); + console.log(error.stack); +} + +function usage() { + console.log('node test/runner.js [OPTIONS] [EXAMPLE1 [EXAMPLE2 ...]]'); + console.log(); + console.log(' -f, --format Choose from: %s.', formatterNames.join(', ')); + console.log(' -h, --help Show this help message.'); +}