├── .gitignore ├── demo ├── add.js ├── main.js ├── test.js ├── mul.js └── bundle.js ├── src ├── finalisers │ ├── index.js │ └── cjs.js ├── utils │ ├── map-helpers.js │ ├── object.js │ ├── promise.js │ └── replaceIdentifiers.js ├── rollup.js ├── ast │ ├── Scope.js │ ├── walk.js │ └── analyse.js ├── external-module.js ├── bundle.js └── module.js ├── README.md ├── .vscode └── launch.json ├── package.json └── dist └── rollup.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /demo/add.js: -------------------------------------------------------------------------------- 1 | export default function add(a, b) { return a + b } -------------------------------------------------------------------------------- /demo/main.js: -------------------------------------------------------------------------------- 1 | import mul from './mul' 2 | 3 | console.log(mul(8, 9)) -------------------------------------------------------------------------------- /src/finalisers/index.js: -------------------------------------------------------------------------------- 1 | const cjs = require('./cjs') 2 | 3 | module.exports = { cjs } -------------------------------------------------------------------------------- /src/utils/map-helpers.js: -------------------------------------------------------------------------------- 1 | function getName(x) { 2 | return x.name 3 | } 4 | 5 | module.exports = { 6 | getName, 7 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 研究 rollup 打包以及 tree-shaking 原理,详情请看文章:[从 rollup 初版源码学习打包原理](https://github.com/woai3c/Front-end-articles/issues/5)。 2 | -------------------------------------------------------------------------------- /demo/test.js: -------------------------------------------------------------------------------- 1 | const rollup = require('../dist/rollup') 2 | 3 | rollup(__dirname + '/main.js').then(res => { 4 | res.wirte('bundle.js') 5 | }) -------------------------------------------------------------------------------- /demo/mul.js: -------------------------------------------------------------------------------- 1 | import add from './add' 2 | 3 | export default function mul(a, b) { 4 | let result = 0 5 | for (let i = 0; i < a; i++) { 6 | result = add(result, b) 7 | } 8 | 9 | return result 10 | } -------------------------------------------------------------------------------- /src/utils/object.js: -------------------------------------------------------------------------------- 1 | const keys = Object.keys 2 | 3 | const hasOwnProp = Object.prototype.hasOwnProperty 4 | 5 | function has(obj, prop) { 6 | return hasOwnProp.call(obj, prop) 7 | } 8 | 9 | module.exports = { 10 | keys, 11 | hasOwnProp, 12 | has, 13 | } -------------------------------------------------------------------------------- /demo/bundle.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | function add(a, b) { return a + b } 4 | 5 | function mul(a, b) { 6 | let result = 0 7 | for (let i = 0; i < a; i++) { 8 | result = add(result, b) 9 | } 10 | 11 | return result 12 | } 13 | 14 | console.log(mul(8, 9)) -------------------------------------------------------------------------------- /src/utils/promise.js: -------------------------------------------------------------------------------- 1 | // 将数组每一项当成参数传给 callback 执行,最后将结果用 promise 返回 2 | function sequence (arr, callback) { 3 | const len = arr.length 4 | const results = new Array(len) 5 | let promise = Promise.resolve() 6 | 7 | function next(i) { 8 | return promise 9 | .then(() => callback(arr[i], i)) 10 | .then(result => results[i] = result) 11 | } 12 | 13 | let i 14 | for (i = 0; i < len; i += 1) { 15 | promise = next(i) 16 | } 17 | 18 | return promise.then(() => results) 19 | } 20 | 21 | module.exports = { sequence } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "skipFiles": [ 12 | "/**" 13 | ], 14 | "program": "${workspaceFolder}\\demo\\test.js" 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "copy-rollup", 3 | "version": "1.0.0", 4 | "description": "拷贝自 rollup 初版源码,并对其进行了部分修改", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "jest" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/woai3c/copy-rollup.git" 12 | }, 13 | "keywords": [], 14 | "author": "", 15 | "license": "MIT", 16 | "bugs": { 17 | "url": "https://github.com/woai3c/copy-rollup/issues" 18 | }, 19 | "homepage": "https://github.com/woai3c/copy-rollup#readme", 20 | "dependencies": { 21 | "acorn": "^8.0.1", 22 | "magic-string": "^0.25.7" 23 | }, 24 | "devDependencies": {} 25 | } 26 | -------------------------------------------------------------------------------- /src/rollup.js: -------------------------------------------------------------------------------- 1 | const Bundle = require('./bundle') 2 | const fs = require('fs') 3 | 4 | function rollup(entry, options = {}) { 5 | const bundle = new Bundle({ entry, ...options }) 6 | return bundle.build().then(() => { 7 | return { 8 | generate: options => bundle.generate(options), 9 | wirte(dest, options = {}) { 10 | const { code } = bundle.generate({ 11 | dest, 12 | format: options.format, 13 | }) 14 | 15 | return fs.writeFile(dest, code, err => { 16 | if (err) throw err 17 | }) 18 | } 19 | } 20 | }) 21 | } 22 | 23 | module.exports = rollup -------------------------------------------------------------------------------- /src/ast/Scope.js: -------------------------------------------------------------------------------- 1 | // 作用域 2 | class Scope { 3 | constructor(options = {}) { 4 | this.parent = options.parent 5 | this.depth = this.parent ? this.parent.depth + 1 : 0 6 | this.names = options.params || [] 7 | this.isBlockScope = !!options.block 8 | } 9 | 10 | add(name, isBlockDeclaration) { 11 | if (!isBlockDeclaration && this.isBlockScope) { 12 | // it's a `var` or function declaration, and this 13 | // is a block scope, so we need to go up 14 | this.parent.add(name, isBlockDeclaration) 15 | } else { 16 | this.names.push(name) 17 | } 18 | } 19 | 20 | contains(name) { 21 | return !!this.findDefiningScope(name) 22 | } 23 | 24 | findDefiningScope(name) { 25 | if (this.names.includes(name)) { 26 | return this 27 | } 28 | 29 | if (this.parent) { 30 | return this.parent.findDefiningScope(name) 31 | } 32 | 33 | return null 34 | } 35 | } 36 | 37 | module.exports = Scope -------------------------------------------------------------------------------- /src/external-module.js: -------------------------------------------------------------------------------- 1 | class ExternalModule { 2 | constructor(id) { 3 | this.id = id 4 | this.name = null 5 | 6 | this.isExternal = true 7 | this.importedByBundle = [] 8 | 9 | this.canonicalNames = {} 10 | this.suggestedNames = {} 11 | 12 | this.needsDefault = false 13 | this.needsNamed = false 14 | } 15 | 16 | getCanonicalName(name) { 17 | if (name === 'default') { 18 | return this.needsNamed ? `${this.name}__default` : this.name 19 | } 20 | 21 | if (name === '*') { 22 | return this.name 23 | } 24 | 25 | // TODO this depends on the output format... works for CJS etc but not ES6 26 | return `${this.name}.${name}` 27 | } 28 | 29 | rename(name, replacement) { 30 | this.canonicalNames[name] = replacement 31 | } 32 | 33 | suggestName(exportName, suggestion) { 34 | if (!this.suggestedNames[exportName]) { 35 | this.suggestedNames[exportName] = suggestion 36 | } 37 | } 38 | } 39 | 40 | module.exports = ExternalModule -------------------------------------------------------------------------------- /src/ast/walk.js: -------------------------------------------------------------------------------- 1 | let shouldSkip 2 | let shouldAbort 3 | // 对 AST 的节点调用 enter() 和 leave() 函数,如果有子节点将递归调用 4 | function walk (ast, { enter, leave }) { 5 | shouldAbort = false 6 | visit(ast, null, enter, leave) 7 | } 8 | 9 | let context = { 10 | skip: () => shouldSkip = true, 11 | abort: () => shouldAbort = true 12 | } 13 | 14 | let childKeys = {} 15 | 16 | let toString = Object.prototype.toString 17 | 18 | function isArray (thing) { 19 | return toString.call(thing) === '[object Array]' 20 | } 21 | 22 | function visit (node, parent, enter, leave) { 23 | if (!node || shouldAbort) return 24 | 25 | if (enter) { 26 | shouldSkip = false 27 | enter.call(context, node, parent) 28 | if (shouldSkip || shouldAbort) return 29 | } 30 | 31 | let keys = childKeys[node.type] || ( 32 | childKeys[node.type] = Object.keys(node).filter(key => typeof node[key] === 'object') 33 | ) 34 | 35 | let key, value, i, j 36 | 37 | i = keys.length 38 | while (i--) { 39 | key = keys[i] 40 | value = node[key] 41 | 42 | if (isArray(value)) { 43 | j = value.length 44 | while (j--) { 45 | visit(value[j], node, enter, leave) 46 | } 47 | } 48 | 49 | else if (value && value.type) { 50 | visit(value, node, enter, leave) 51 | } 52 | } 53 | 54 | if (leave && !shouldAbort) { 55 | leave(node, parent) 56 | } 57 | } 58 | 59 | module.exports = walk -------------------------------------------------------------------------------- /src/finalisers/cjs.js: -------------------------------------------------------------------------------- 1 | const { keys } = require('../utils/object') 2 | 3 | function cjs(bundle, magicString, exportMode) { 4 | let intro = `'use strict'\n\n` 5 | 6 | const importBlock = bundle.externalModules 7 | .map(module => { 8 | let requireStatement = `var ${module.name} = require('${module.id}')` 9 | 10 | if (module.needsDefault) { 11 | requireStatement += '\n' + (module.needsNamed ? `var ${module.name}__default = ` : `${module.name} = `) + 12 | `'default' in ${module.name} ? ${module.name}['default'] : ${module.name}` 13 | } 14 | 15 | return requireStatement 16 | }) 17 | .join('\n') 18 | 19 | if (importBlock) { 20 | intro += importBlock + '\n\n' 21 | } 22 | 23 | magicString.prepend(intro) 24 | 25 | let exportBlock 26 | if (exportMode === 'default' && bundle.entryModule.exports.default) { 27 | exportBlock = `module.exports = ${bundle.entryModule.getCanonicalName('default')}` 28 | } else if (exportMode === 'named') { 29 | exportBlock = keys(bundle.entryModule.exports) 30 | .map(key => { 31 | const specifier = bundle.entryModule.exports[key] 32 | const name = bundle.entryModule.getCanonicalName(specifier.localName) 33 | 34 | return `exports.${key} = ${name}` 35 | }) 36 | .join('\n') 37 | } 38 | 39 | if (exportBlock) { 40 | magicString.append('\n\n' + exportBlock) 41 | } 42 | 43 | return magicString 44 | } 45 | 46 | module.exports = cjs -------------------------------------------------------------------------------- /src/utils/replaceIdentifiers.js: -------------------------------------------------------------------------------- 1 | const walk = require('../ast/walk') 2 | const { has } = require('./object') 3 | 4 | // 重写 node 名称 5 | // 例如 import { resolve } from path; 将 resolve 变为 path.resolve 6 | function replaceIdentifiers(statement, snippet, names) { 7 | const replacementStack = [names] 8 | const keys = Object.keys(names) 9 | 10 | if (keys.length === 0) { 11 | return 12 | } 13 | 14 | walk(statement, { 15 | enter(node, parent) { 16 | const scope = node._scope 17 | 18 | if (scope) { 19 | let newNames = {} 20 | let hasReplacements 21 | 22 | keys.forEach(key => { 23 | if (!scope.names.includes(key)) { 24 | newNames[key] = names[key] 25 | hasReplacements = true 26 | } 27 | }) 28 | 29 | if (!hasReplacements) { 30 | return this.skip() 31 | } 32 | 33 | names = newNames 34 | replacementStack.push(newNames) 35 | } 36 | 37 | // We want to rewrite identifiers (that aren't property names) 38 | if (node.type !== 'Identifier') return 39 | if (parent.type === 'MemberExpression' && node !== parent.object) return 40 | if (parent.type === 'Property' && node !== parent.value) return 41 | 42 | const name = has(names, node.name) && names[node.name] 43 | 44 | if (name && name !== node.name) { 45 | snippet.overwrite(node.start, node.end, name) 46 | } 47 | }, 48 | 49 | leave(node) { 50 | if (node._scope) { 51 | replacementStack.pop() 52 | names = replacementStack[replacementStack.length - 1] 53 | } 54 | } 55 | }) 56 | } 57 | 58 | module.exports = replaceIdentifiers -------------------------------------------------------------------------------- /src/ast/analyse.js: -------------------------------------------------------------------------------- 1 | const walk = require('./walk') 2 | const Scope = require('./scope') 3 | const { getName } = require('../utils/map-helpers') 4 | 5 | // 对 AST 进行分析,按节点层级赋予对应的作用域,并找出有哪些依赖项和对依赖项作了哪些修改 6 | function analyse(ast, magicString, module) { 7 | let scope = new Scope() 8 | let currentTopLevelStatement 9 | 10 | function addToScope(declarator) { 11 | var name = declarator.id.name 12 | scope.add(name, false) 13 | 14 | if (!scope.parent) { 15 | currentTopLevelStatement._defines[name] = true 16 | } 17 | } 18 | 19 | function addToBlockScope(declarator) { 20 | var name = declarator.id.name 21 | scope.add(name, true) 22 | 23 | if (!scope.parent) { 24 | currentTopLevelStatement._defines[name] = true 25 | } 26 | } 27 | 28 | // first we need to generate comprehensive scope info 29 | let previousStatement = null 30 | 31 | // 为每个语句定义作用域,并将父子作用域关联起来 32 | ast.body.forEach(statement => { 33 | currentTopLevelStatement = statement // so we can attach scoping info 34 | 35 | // 这些属性不能遍历 36 | Object.defineProperties(statement, { 37 | _defines: { value: {} }, 38 | _modifies: { value: {} }, 39 | _dependsOn: { value: {} }, 40 | _included: { value: false, writable: true }, 41 | _module: { value: module }, 42 | _source: { value: magicString.snip(statement.start, statement.end) }, 43 | _margin: { value: [0, 0] }, 44 | }) 45 | 46 | // determine margin 47 | const previousEnd = previousStatement ? previousStatement.end : 0 48 | const start = statement.start 49 | 50 | const gap = magicString.original.slice(previousEnd, start) 51 | const margin = gap.split('\n').length 52 | 53 | if (previousStatement) previousStatement._margin[1] = margin 54 | statement._margin[0] = margin 55 | 56 | walk(statement, { 57 | enter (node) { 58 | let newScope 59 | switch (node.type) { 60 | case 'FunctionExpression': 61 | case 'FunctionDeclaration': 62 | case 'ArrowFunctionExpression': 63 | const names = node.params.map(getName) 64 | 65 | if (node.type === 'FunctionDeclaration') { 66 | addToScope(node) 67 | } else if (node.type === 'FunctionExpression' && node.id) { 68 | names.push(node.id.name) 69 | } 70 | 71 | newScope = new Scope({ 72 | parent: scope, 73 | params: names, // TODO rest params? 74 | block: false 75 | }) 76 | 77 | break 78 | 79 | case 'BlockStatement': 80 | newScope = new Scope({ 81 | parent: scope, 82 | block: true 83 | }) 84 | 85 | break 86 | 87 | case 'CatchClause': 88 | newScope = new Scope({ 89 | parent: scope, 90 | params: [node.param.name], 91 | block: true 92 | }) 93 | 94 | break 95 | 96 | case 'VariableDeclaration': 97 | node.declarations.forEach(node.kind === 'let' ? addToBlockScope : addToScope) // TODO const? 98 | break 99 | 100 | case 'ClassDeclaration': 101 | addToScope(node) 102 | break 103 | } 104 | 105 | if (newScope) { 106 | Object.defineProperty(node, '_scope', { value: newScope }) 107 | scope = newScope 108 | } 109 | }, 110 | leave (node) { 111 | if (node === currentTopLevelStatement) { 112 | currentTopLevelStatement = null 113 | } 114 | 115 | if (node._scope) { 116 | scope = scope.parent 117 | } 118 | } 119 | }) 120 | 121 | previousStatement = statement 122 | }) 123 | 124 | // then, we need to find which top-level dependencies this statement has, 125 | // and which it potentially modifies 126 | // 然后,我们需要找出这个语句有哪些顶级依赖项,以及它可能修改哪些依赖项 127 | ast.body.forEach(statement => { 128 | function checkForReads (node, parent) { 129 | // 节点类型为 Identifier,并且不存在 statement 作用域中,说明它是顶级依赖项 130 | if (node.type === 'Identifier') { 131 | // disregard the `bar` in `foo.bar` - these appear as Identifier nodes 132 | if (parent.type === 'MemberExpression' && node !== parent.object) { 133 | return 134 | } 135 | 136 | // disregard the `bar` in { bar: foo } 137 | if (parent.type === 'Property' && node !== parent.value) { 138 | return 139 | } 140 | 141 | const definingScope = scope.findDefiningScope(node.name) 142 | 143 | if ((!definingScope || definingScope.depth === 0) && !statement._defines[node.name]) { 144 | statement._dependsOn[node.name] = true 145 | } 146 | } 147 | 148 | } 149 | // 检查有没修改依赖 150 | function checkForWrites(node) { 151 | function addNode (node, disallowImportReassignments) { 152 | while (node.type === 'MemberExpression') { 153 | node = node.object 154 | } 155 | 156 | if (node.type !== 'Identifier') { 157 | return 158 | } 159 | 160 | statement._modifies[node.name] = true 161 | } 162 | 163 | // 检查 a = 1 + 2 中的 a 是否被修改 164 | // 如果 a 是引入模块并且被修改就报错 165 | if (node.type === 'AssignmentExpression') { 166 | addNode(node.left, true) 167 | } 168 | // a++/a-- 169 | else if (node.type === 'UpdateExpression') { 170 | addNode(node.argument, true) 171 | } else if (node.type === 'CallExpression') { 172 | node.arguments.forEach(arg => addNode(arg, false)) 173 | } 174 | } 175 | 176 | walk(statement, { 177 | enter (node, parent) { 178 | // skip imports 179 | if (/^Import/.test(node.type)) return this.skip() 180 | 181 | if (node._scope) scope = node._scope 182 | 183 | checkForReads(node, parent) 184 | checkForWrites(node, parent) 185 | }, 186 | leave (node) { 187 | if (node._scope) scope = scope.parent 188 | } 189 | }) 190 | }) 191 | 192 | ast._scope = scope 193 | } 194 | 195 | module.exports = analyse -------------------------------------------------------------------------------- /src/bundle.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const fs = require('fs') 3 | const Module = require('./module') 4 | const MagicString = require('magic-string') 5 | const { has, keys } = require('./utils/object') 6 | const finalisers = require('./finalisers') 7 | const ExternalModule = require('./external-module') 8 | const replaceIdentifiers = require('./utils/replaceIdentifiers') 9 | 10 | class Bundle { 11 | constructor(options = {}) { 12 | // 防止用户省略 .js 后缀 13 | this.entryPath = path.resolve(options.entry.replace(/\.js$/, '') + '.js') 14 | // 获取入口文件的目录 15 | this.base = path.dirname(this.entryPath) 16 | // 入口模块 17 | this.entryModule = null 18 | // 读取过的模块都缓存在此,如果重复读取则直接从缓存读取模块,提高效率 19 | this.modules = {} 20 | // 最后真正要生成的代码的 AST 节点语句,不用生成的 AST 会被省略掉 21 | this.statements = [] 22 | // 外部模块,当通过路径获取不到的模块就属于外部模块,例如 const fs = require('fs') 中的 fs 模块 23 | this.externalModules = [] 24 | // import * as test from './foo' 需要用到 25 | this.internalNamespaceModules = [] 26 | } 27 | 28 | build() { 29 | return this.fetchModule(this.entryPath) 30 | .then(entryModule => { 31 | this.entryModule = entryModule 32 | return entryModule.expandAllStatements(true) 33 | }) 34 | .then(statements => { 35 | this.statements = statements 36 | this.deconflict() 37 | }) 38 | } 39 | 40 | // importee 被调用模块文件 41 | // importer 调用模块文件 42 | // 例如在入口文件 main.js 中引入了另一个文件 foo.js 中的函数 43 | // 此时 main.js 就是 importer,而 foo.js 是 importee 44 | fetchModule(importee, importer) { 45 | return new Promise((resolve, reject) => { 46 | // 如果有缓存,则直接返回 47 | if (this.modules[importee]) { 48 | resolve(this.modules[importee]) 49 | return 50 | } 51 | 52 | let route 53 | // 入口文件没有 importer 54 | if (!importer) { 55 | route = importee 56 | } else { 57 | // 绝对路径 58 | if (path.isAbsolute(importee)) { 59 | route = importee 60 | } else if (importee[0] == '.') { 61 | // 相对路径 62 | // 获取 importer 的目录,从而找到 importee 的绝对路径 63 | route = path.resolve(path.dirname(importer), importee.replace(/\.js$/, '') + '.js') 64 | } 65 | } 66 | 67 | if (route) { 68 | fs.readFile(route, 'utf-8', (err, code) => { 69 | if (err) reject(err) 70 | const module = new Module({ 71 | code, 72 | path: route, 73 | bundle: this, 74 | }) 75 | 76 | this.modules[route] = module 77 | resolve(module) 78 | }) 79 | } else { 80 | // 没有找到路径则是外部模块 81 | const module = new ExternalModule(importee) 82 | this.externalModules.push(module) 83 | this.modules[importee] = module 84 | resolve(module) 85 | } 86 | }) 87 | } 88 | 89 | generate(options = {}) { 90 | let magicString = new MagicString.Bundle({ separator: '' }) 91 | // Determine export mode - 'default', 'named', 'none' 92 | // 导出模式 93 | let exportMode = this.getExportMode(options.exports) 94 | let previousMargin = 0 95 | 96 | // Apply new names and add to the output bundle 97 | this.statements.forEach(statement => { 98 | let replacements = {} 99 | 100 | keys(statement._dependsOn) 101 | .concat(keys(statement._defines)) 102 | .forEach(name => { 103 | const canonicalName = statement._module.getCanonicalName(name) 104 | 105 | if (name !== canonicalName) { 106 | replacements[name] = canonicalName 107 | } 108 | }) 109 | 110 | const source = statement._source.clone().trim() 111 | 112 | // modify exports as necessary 113 | if (/^Export/.test(statement.type)) { 114 | // 已经引入到一起打包了,所以不需要这些语句了 115 | // 跳过 `export { foo, bar, baz }` 语句 116 | if (statement.type === 'ExportNamedDeclaration' && statement.specifiers.length) { 117 | return 118 | } 119 | 120 | // 因为已经打包在一起了 121 | // 如果引入的模块是 export var foo = 42,就移除 export,变成 var foo = 42 122 | if (statement.type === 'ExportNamedDeclaration' && statement.declaration.type === 'VariableDeclaration') { 123 | source.remove(statement.start, statement.declaration.start) 124 | } 125 | // `export class Foo {...}` 移除 export 126 | else if (statement.declaration.id) { 127 | source.remove(statement.start, statement.declaration.start) 128 | } else if (statement.type === 'ExportDefaultDeclaration') { 129 | const module = statement._module 130 | const canonicalName = module.getCanonicalName('default') 131 | 132 | if (statement.declaration.type === 'Identifier' && canonicalName === module.getCanonicalName(statement.declaration.name)) { 133 | return 134 | } 135 | 136 | source.overwrite(statement.start, statement.declaration.start, `var ${canonicalName} = `) 137 | } else { 138 | throw new Error('Unhandled export') 139 | } 140 | } 141 | 142 | // 例如 import { resolve } from path; 将 resolve 变为 path.resolve 143 | replaceIdentifiers(statement, source, replacements) 144 | 145 | // 生成空行 146 | // add margin 147 | const margin = Math.max(statement._margin[0], previousMargin) 148 | const newLines = new Array(margin).join('\n') 149 | 150 | // add the statement itself 151 | magicString.addSource({ 152 | content: source, 153 | separator: newLines 154 | }) 155 | 156 | previousMargin = statement._margin[1] 157 | }) 158 | 159 | // 这个主要是针对 import * as g from './foo' 语句 160 | // 如果 foo 文件有默认导出的函数和 two() 函数,生成的代码如下 161 | // var g = { 162 | // get default () { return g__default }, 163 | // get two () { return two } 164 | // } 165 | const indentString = magicString.getIndentString() 166 | const namespaceBlock = this.internalNamespaceModules.map(module => { 167 | const exportKeys = keys(module.exports) 168 | 169 | return `var ${module.getCanonicalName('*')} = {\n` + 170 | exportKeys.map(key => `${indentString}get ${key} () { return ${module.getCanonicalName(key)} }`).join(',\n') + 171 | `\n}\n\n` 172 | }).join('') 173 | 174 | magicString.prepend(namespaceBlock) 175 | 176 | const finalise = finalisers[options.format || 'cjs'] 177 | magicString = finalise(this, magicString.trim(), exportMode, options) 178 | 179 | return { code: magicString.toString() } 180 | } 181 | 182 | getExportMode(exportMode) { 183 | const exportKeys = keys(this.entryModule.exports) 184 | 185 | if (!exportMode || exportMode === 'auto') { 186 | if (exportKeys.length === 0) { 187 | // 没有导出模块 188 | exportMode = 'none' 189 | } else if (exportKeys.length === 1 && exportKeys[0] === 'default') { 190 | // 只有一个导出模块,并且是 default 191 | exportMode = 'default' 192 | } else { 193 | exportMode = 'named' 194 | } 195 | } 196 | 197 | return exportMode 198 | } 199 | 200 | deconflict() { 201 | const definers = {} 202 | const conflicts = {} 203 | // 解决冲突,例如两个不同的模块有一个同名函数,则需要对其中一个重命名。 204 | this.statements.forEach(statement => { 205 | keys(statement._defines).forEach(name => { 206 | if (has(definers, name)) { 207 | conflicts[name] = true 208 | } else { 209 | definers[name] = [] 210 | } 211 | 212 | definers[name].push(statement._module) 213 | }) 214 | }) 215 | 216 | // 为外部模块分配名称,例如引入了 path 模块的 resolve 方法,使用时直接用 resolve() 217 | // 打包后会变成 path.resolve 218 | this.externalModules.forEach(module => { 219 | const name = module.suggestedNames['*'] || module.suggestedNames.default || module.id 220 | 221 | if (has(definers, name)) { 222 | conflicts[name] = true 223 | } else { 224 | definers[name] = [] 225 | } 226 | 227 | definers[name].push(module) 228 | module.name = name 229 | }) 230 | 231 | // Rename conflicting identifiers so they can live in the same scope 232 | keys(conflicts).forEach(name => { 233 | const modules = definers[name] 234 | // 最靠近入口模块的模块可以保持原样,即不改名 235 | modules.pop() 236 | // 其他冲突的模块要改名 237 | // 改名就是在冲突的变量前加下划线 _ 238 | modules.forEach(module => { 239 | const replacement = getSafeName(name) 240 | module.rename(name, replacement) 241 | }) 242 | }) 243 | 244 | function getSafeName(name) { 245 | while (has(conflicts, name)) { 246 | name = `_${name}` 247 | } 248 | 249 | conflicts[name] = true 250 | return name 251 | } 252 | } 253 | } 254 | 255 | module.exports = Bundle -------------------------------------------------------------------------------- /src/module.js: -------------------------------------------------------------------------------- 1 | const { parse } = require('acorn') 2 | const analyse = require('./ast/analyse') 3 | const MagicString = require('magic-string') 4 | const { has, keys } = require('./utils/object') 5 | const { sequence } = require('./utils/promise') 6 | 7 | const emptyArrayPromise = Promise.resolve([]) 8 | 9 | class Module { 10 | constructor({ code, path, bundle }) { 11 | this.code = new MagicString(code, { 12 | filename: path 13 | }) 14 | 15 | this.path = path 16 | this.bundle = bundle 17 | this.suggestedNames = {} 18 | this.ast = parse(code, { 19 | ecmaVersion: 6, 20 | sourceType: 'module', 21 | }) 22 | 23 | this.analyse() 24 | } 25 | 26 | // 分析导入和导出的模块,将引入的模块和导出的模块填入对应的数组 27 | analyse() { 28 | this.imports = {} 29 | this.exports = {} 30 | 31 | this.ast.body.forEach(node => { 32 | let source 33 | 34 | // import foo from './foo' 35 | // import { bar } from './bar' 36 | if (node.type === 'ImportDeclaration') { 37 | source = node.source.value 38 | node.specifiers.forEach(specifier => { 39 | // import foo from './foo' 40 | const isDefault = specifier.type == 'ImportDefaultSpecifier' 41 | // import * as foo from './foo' 42 | const isNamespace = specifier.type == 'ImportNamespaceSpecifier' 43 | 44 | const localName = specifier.local.name 45 | const name = isDefault ? 'default' 46 | : isNamespace ? '*' : specifier.imported.name 47 | 48 | this.imports[localName] = { 49 | source, 50 | name, 51 | localName 52 | } 53 | }) 54 | } else if (/^Export/.test(node.type)) { 55 | // export default function foo () {} 56 | // export default foo 57 | // export default 42 58 | if (node.type === 'ExportDefaultDeclaration') { 59 | const isDeclaration = /Declaration$/.test(node.declaration.type) 60 | this.exports.default = { 61 | node, 62 | name: 'default', 63 | localName: isDeclaration ? node.declaration.id.name : 'default', 64 | isDeclaration 65 | } 66 | } else if (node.type === 'ExportNamedDeclaration') { 67 | // export { foo, bar, baz } 68 | // export var foo = 42 69 | // export function foo () {} 70 | // export { foo } from './foo' 71 | source = node.source && node.source.value 72 | 73 | if (node.specifiers.length) { 74 | // export { foo, bar, baz } 75 | node.specifiers.forEach(specifier => { 76 | const localName = specifier.local.name 77 | const exportedName = specifier.exported.name 78 | 79 | this.exports[exportedName] = { 80 | localName, 81 | exportedName 82 | } 83 | 84 | // export { foo } from './foo' 85 | // 这种格式还需要引入相应的模块,例如上述例子要引入 './foo' 模块 86 | if (source) { 87 | this.imports[localName] = { 88 | source, 89 | localName, 90 | name: exportedName 91 | } 92 | } 93 | }) 94 | } else { 95 | const declaration = node.declaration 96 | let name 97 | 98 | if (declaration.type === 'VariableDeclaration') { 99 | // export var foo = 42 100 | name = declaration.declarations[0].id.name 101 | } else { 102 | // export function foo () {} 103 | name = declaration.id.name 104 | } 105 | 106 | this.exports[name] = { 107 | node, 108 | localName: name, 109 | expression: declaration 110 | } 111 | } 112 | } 113 | } 114 | }) 115 | 116 | // 调用 ast 目录下的 analyse() 117 | analyse(this.ast, this.code, this) 118 | // 当前模块下的顶级变量(包括函数声明) 119 | this.definedNames = this.ast._scope.names.slice() 120 | this.canonicalNames = {} 121 | this.definitions = {} 122 | this.definitionPromises = {} 123 | this.modifications = {} 124 | 125 | this.ast.body.forEach(statement => { 126 | // 读取当前语句下的变量 127 | Object.keys(statement._defines).forEach(name => { 128 | this.definitions[name] = statement 129 | }) 130 | 131 | // 再根据 _modifies 修改它们,_modifies 是在 analyse() 中改变的 132 | Object.keys(statement._modifies).forEach(name => { 133 | if (!has(this.modifications, name)) { 134 | this.modifications[name] = [] 135 | } 136 | 137 | this.modifications[name].push(statement) 138 | }) 139 | }) 140 | } 141 | 142 | expandAllStatements(isEntryModule) { 143 | let allStatements = [] 144 | 145 | return sequence(this.ast.body, statement => { 146 | // skip already-included statements 147 | if (statement._included) return 148 | 149 | // 不需要对导入语句作处理 150 | if (statement.type === 'ImportDeclaration') { 151 | return 152 | } 153 | 154 | // skip `export { foo, bar, baz }` 155 | if (statement.type === 'ExportNamedDeclaration' && statement.specifiers.length) { 156 | // but ensure they are defined, if this is the entry module 157 | // export { foo, bar, baz } 158 | // 遇到这样的语句,如果是从其他模块引入的函数,则会去对应的模块加载函数, 159 | if (isEntryModule) { 160 | return this.expandStatement(statement) 161 | .then(statements => { 162 | allStatements.push.apply(allStatements, statements) 163 | }) 164 | } 165 | 166 | return 167 | } 168 | 169 | // 剩下的其他类型语句则要添加到 allStatements 中,以待在 bundle.generate() 中生成 170 | // include everything else 171 | return this.expandStatement(statement) 172 | .then(statements => { 173 | allStatements.push.apply(allStatements, statements) 174 | }) 175 | }).then(() => { 176 | return allStatements 177 | }) 178 | } 179 | 180 | expandStatement(statement) { 181 | if (statement._included) return emptyArrayPromise 182 | statement._included = true 183 | 184 | let result = [] 185 | 186 | // 根据 AST 节点的依赖项找到相应的模块 187 | // 例如依赖 path 模块,就需要去找到它 188 | const dependencies = Object.keys(statement._dependsOn) 189 | 190 | return sequence(dependencies, name => { 191 | // define() 将从其他模块中引入的函数加载进来 192 | return this.define(name).then(definition => { 193 | result.push.apply(result, definition) 194 | }) 195 | }) 196 | 197 | // then include the statement itself 198 | .then(() => { 199 | result.push(statement) 200 | }) 201 | .then(() => { 202 | // then include any statements that could modify the 203 | // thing(s) this statement defines 204 | return sequence(keys(statement._defines), name => { 205 | const modifications = has(this.modifications, name) && this.modifications[name] 206 | 207 | if (modifications) { 208 | return sequence(modifications, statement => { 209 | if (!statement._included) { 210 | return this.expandStatement(statement) 211 | .then(statements => { 212 | result.push.apply(result, statements) 213 | }) 214 | } 215 | }) 216 | } 217 | }) 218 | }) 219 | .then(() => { 220 | // the `result` is an array of statements needed to define `name` 221 | return result 222 | }) 223 | } 224 | 225 | define(name) { 226 | if (has(this.definitionPromises, name)) { 227 | return emptyArrayPromise 228 | } 229 | 230 | let promise 231 | 232 | // The definition for this name is in a different module 233 | if (has(this.imports, name)) { 234 | const importDeclaration = this.imports[name] 235 | 236 | promise = this.bundle.fetchModule(importDeclaration.source, this.path) 237 | .then(module => { 238 | importDeclaration.module = module 239 | 240 | // suggest names. TODO should this apply to non default/* imports? 241 | if (importDeclaration.name === 'default') { 242 | // TODO this seems ropey 243 | const localName = importDeclaration.localName 244 | const suggestion = has(this.suggestedNames, localName) ? this.suggestedNames[localName] : localName 245 | module.suggestName('default', suggestion) 246 | } else if (importDeclaration.name === '*') { 247 | const localName = importDeclaration.localName 248 | const suggestion = has(this.suggestedNames, localName) ? this.suggestedNames[localName] : localName 249 | module.suggestName('*', suggestion) 250 | module.suggestName('default', `${suggestion}__default`) 251 | } 252 | 253 | if (module.isExternal) { 254 | if (importDeclaration.name === 'default') { 255 | module.needsDefault = true 256 | } else { 257 | module.needsNamed = true 258 | } 259 | 260 | module.importedByBundle.push(importDeclaration) 261 | return emptyArrayPromise 262 | } 263 | 264 | if (importDeclaration.name === '*') { 265 | // we need to create an internal namespace 266 | if (!this.bundle.internalNamespaceModules.includes(module)) { 267 | this.bundle.internalNamespaceModules.push(module) 268 | } 269 | 270 | return module.expandAllStatements() 271 | } 272 | 273 | const exportDeclaration = module.exports[importDeclaration.name] 274 | 275 | if (!exportDeclaration) { 276 | throw new Error(`Module ${module.path} does not export ${importDeclaration.name} (imported by ${this.path})`) 277 | } 278 | 279 | return module.define(exportDeclaration.localName) 280 | }) 281 | } 282 | // The definition is in this module 283 | else if (name === 'default' && this.exports.default.isDeclaration) { 284 | // We have something like `export default foo` - so we just start again, 285 | // searching for `foo` instead of default 286 | promise = this.define(this.exports.default.name) 287 | } else { 288 | let statement 289 | 290 | if (name === 'default') { 291 | // TODO can we use this.definitions[name], as below? 292 | statement = this.exports.default.node 293 | } else { 294 | statement = this.definitions[name] 295 | } 296 | 297 | if (statement && !statement._included) { 298 | promise = this.expandStatement(statement) 299 | } 300 | } 301 | 302 | this.definitionPromises[name] = promise || emptyArrayPromise 303 | return this.definitionPromises[name] 304 | } 305 | 306 | getCanonicalName(localName) { 307 | if (has(this.suggestedNames, localName)) { 308 | localName = this.suggestedNames[localName] 309 | } 310 | 311 | if (!has(this.canonicalNames, localName)) { 312 | let canonicalName 313 | 314 | if (has(this.imports, localName)) { 315 | const importDeclaration = this.imports[localName] 316 | const module = importDeclaration.module 317 | 318 | if (importDeclaration.name === '*') { 319 | canonicalName = module.suggestedNames['*'] 320 | } else { 321 | let exporterLocalName 322 | 323 | if (module.isExternal) { 324 | exporterLocalName = importDeclaration.name 325 | } else { 326 | const exportDeclaration = module.exports[importDeclaration.name] 327 | exporterLocalName = exportDeclaration.localName 328 | } 329 | 330 | canonicalName = module.getCanonicalName(exporterLocalName) 331 | } 332 | } else { 333 | canonicalName = localName 334 | } 335 | 336 | this.canonicalNames[localName] = canonicalName 337 | } 338 | 339 | return this.canonicalNames[localName] 340 | } 341 | 342 | rename(name, replacement) { 343 | this.canonicalNames[name] = replacement 344 | } 345 | 346 | suggestName(exportName, suggestion) { 347 | if (!this.suggestedNames[exportName]) { 348 | this.suggestedNames[exportName] = suggestion 349 | } 350 | } 351 | } 352 | 353 | module.exports = Module -------------------------------------------------------------------------------- /dist/rollup.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const MagicString = require('magic-string') 4 | const { parse } = require('acorn') 5 | 6 | function rollup(entry, options = {}) { 7 | const bundle = new Bundle({ entry, ...options }) 8 | return bundle.build().then(() => { 9 | return { 10 | generate: options => bundle.generate(options), 11 | wirte(dest, options = {}) { 12 | const { code } = bundle.generate({ 13 | dest, 14 | format: options.format, 15 | }) 16 | 17 | return fs.writeFile(dest, code, err => { 18 | if (err) throw err 19 | }) 20 | } 21 | } 22 | }) 23 | } 24 | 25 | class Bundle { 26 | constructor(options = {}) { 27 | // 防止用户省略 .js 后缀 28 | this.entryPath = path.resolve(options.entry.replace(/\.js$/, '') + '.js') 29 | // 获取入口文件的目录 30 | this.base = path.dirname(this.entryPath) 31 | // 入口模块 32 | this.entryModule = null 33 | // 读取过的模块都缓存在此,如果重复读取则直接从缓存读取模块,提高效率 34 | this.modules = {} 35 | // 最后真正要生成的代码的 AST 节点语句,不用生成的 AST 会被省略掉 36 | this.statements = [] 37 | // 外部模块,当通过路径获取不到的模块就属于外部模块,例如 const fs = require('fs') 中的 fs 模块 38 | this.externalModules = [] 39 | // import * as test from './foo' 需要用到 40 | this.internalNamespaceModules = [] 41 | } 42 | 43 | build() { 44 | return this.fetchModule(this.entryPath) 45 | .then(entryModule => { 46 | this.entryModule = entryModule 47 | return entryModule.expandAllStatements(true) 48 | }) 49 | .then(statements => { 50 | this.statements = statements 51 | this.deconflict() 52 | }) 53 | } 54 | 55 | // importee 被调用模块文件 56 | // importer 调用模块文件 57 | // 例如在入口文件 main.js 中引入了另一个文件 foo.js 中的函数 58 | // 此时 main.js 就是 importer,而 foo.js 是 importee 59 | fetchModule(importee, importer) { 60 | return new Promise((resolve, reject) => { 61 | // 如果有缓存,则直接返回 62 | if (this.modules[importee]) { 63 | resolve(this.modules[importee]) 64 | return 65 | } 66 | 67 | let route 68 | // 入口文件没有 importer 69 | if (!importer) { 70 | route = importee 71 | } else { 72 | // 绝对路径 73 | if (path.isAbsolute(importee)) { 74 | route = importee 75 | } else if (importee[0] == '.') { 76 | // 相对路径 77 | // 获取 importer 的目录,从而找到 importee 的绝对路径 78 | route = path.resolve(path.dirname(importer), importee.replace(/\.js$/, '') + '.js') 79 | } 80 | } 81 | 82 | if (route) { 83 | fs.readFile(route, 'utf-8', (err, code) => { 84 | if (err) reject(err) 85 | const module = new Module({ 86 | code, 87 | path: route, 88 | bundle: this, 89 | }) 90 | 91 | this.modules[route] = module 92 | resolve(module) 93 | }) 94 | } else { 95 | // 没有找到路径则是外部模块 96 | const module = new ExternalModule(importee) 97 | this.externalModules.push(module) 98 | this.modules[importee] = module 99 | resolve(module) 100 | } 101 | }) 102 | } 103 | 104 | generate(options = {}) { 105 | let magicString = new MagicString.Bundle({ separator: '' }) 106 | // Determine export mode - 'default', 'named', 'none' 107 | // 导出模式 108 | let exportMode = this.getExportMode(options.exports) 109 | let previousMargin = 0 110 | 111 | // Apply new names and add to the output bundle 112 | this.statements.forEach(statement => { 113 | let replacements = {} 114 | 115 | keys(statement._dependsOn) 116 | .concat(keys(statement._defines)) 117 | .forEach(name => { 118 | const canonicalName = statement._module.getCanonicalName(name) 119 | 120 | if (name !== canonicalName) { 121 | replacements[name] = canonicalName 122 | } 123 | }) 124 | 125 | const source = statement._source.clone().trim() 126 | 127 | // modify exports as necessary 128 | if (/^Export/.test(statement.type)) { 129 | // 已经引入到一起打包了,所以不需要这些语句了 130 | // 跳过 `export { foo, bar, baz }` 语句 131 | if (statement.type === 'ExportNamedDeclaration' && statement.specifiers.length) { 132 | return 133 | } 134 | 135 | // 因为已经打包在一起了 136 | // 如果引入的模块是 export var foo = 42,就移除 export,变成 var foo = 42 137 | if (statement.type === 'ExportNamedDeclaration' && statement.declaration.type === 'VariableDeclaration') { 138 | source.remove(statement.start, statement.declaration.start) 139 | } 140 | // `export class Foo {...}` 移除 export 141 | else if (statement.declaration.id) { 142 | source.remove(statement.start, statement.declaration.start) 143 | } else if (statement.type === 'ExportDefaultDeclaration') { 144 | const module = statement._module 145 | const canonicalName = module.getCanonicalName('default') 146 | 147 | if (statement.declaration.type === 'Identifier' && canonicalName === module.getCanonicalName(statement.declaration.name)) { 148 | return 149 | } 150 | 151 | source.overwrite(statement.start, statement.declaration.start, `var ${canonicalName} = `) 152 | } else { 153 | throw new Error('Unhandled export') 154 | } 155 | } 156 | 157 | // 例如 import { resolve } from path; 将 resolve 变为 path.resolve 158 | replaceIdentifiers(statement, source, replacements) 159 | 160 | // 生成空行 161 | // add margin 162 | const margin = Math.max(statement._margin[0], previousMargin) 163 | const newLines = new Array(margin).join('\n') 164 | 165 | // add the statement itself 166 | magicString.addSource({ 167 | content: source, 168 | separator: newLines 169 | }) 170 | 171 | previousMargin = statement._margin[1] 172 | }) 173 | 174 | // 这个主要是针对 import * as g from './foo' 语句 175 | // 如果 foo 文件有默认导出的函数和 two() 函数,生成的代码如下 176 | // var g = { 177 | // get default () { return g__default }, 178 | // get two () { return two } 179 | // } 180 | const indentString = magicString.getIndentString() 181 | const namespaceBlock = this.internalNamespaceModules.map(module => { 182 | const exportKeys = keys(module.exports) 183 | 184 | return `var ${module.getCanonicalName('*')} = {\n` + 185 | exportKeys.map(key => `${indentString}get ${key} () { return ${module.getCanonicalName(key)} }`).join(',\n') + 186 | `\n}\n\n` 187 | }).join('') 188 | 189 | magicString.prepend(namespaceBlock) 190 | 191 | magicString = cjs(this, magicString.trim(), exportMode, options) 192 | 193 | return { code: magicString.toString() } 194 | } 195 | 196 | getExportMode(exportMode) { 197 | const exportKeys = keys(this.entryModule.exports) 198 | 199 | if (!exportMode || exportMode === 'auto') { 200 | if (exportKeys.length === 0) { 201 | // 没有导出模块 202 | exportMode = 'none' 203 | } else if (exportKeys.length === 1 && exportKeys[0] === 'default') { 204 | // 只有一个导出模块,并且是 default 205 | exportMode = 'default' 206 | } else { 207 | exportMode = 'named' 208 | } 209 | } 210 | 211 | return exportMode 212 | } 213 | 214 | deconflict() { 215 | const definers = {} 216 | const conflicts = {} 217 | // 解决冲突,例如两个不同的模块有一个同名函数,则需要对其中一个重命名。 218 | this.statements.forEach(statement => { 219 | keys(statement._defines).forEach(name => { 220 | if (has(definers, name)) { 221 | conflicts[name] = true 222 | } else { 223 | definers[name] = [] 224 | } 225 | 226 | definers[name].push(statement._module) 227 | }) 228 | }) 229 | 230 | // 为外部模块分配名称,例如引入了 path 模块的 resolve 方法,使用时直接用 resolve() 231 | // 打包后会变成 path.resolve 232 | this.externalModules.forEach(module => { 233 | const name = module.suggestedNames['*'] || module.suggestedNames.default || module.id 234 | 235 | if (has(definers, name)) { 236 | conflicts[name] = true 237 | } else { 238 | definers[name] = [] 239 | } 240 | 241 | definers[name].push(module) 242 | module.name = name 243 | }) 244 | 245 | // Rename conflicting identifiers so they can live in the same scope 246 | keys(conflicts).forEach(name => { 247 | const modules = definers[name] 248 | // 最靠近入口模块的模块可以保持原样,即不改名 249 | modules.pop() 250 | // 其他冲突的模块要改名 251 | // 改名就是在冲突的变量前加下划线 _ 252 | modules.forEach(module => { 253 | const replacement = getSafeName(name) 254 | module.rename(name, replacement) 255 | }) 256 | }) 257 | 258 | function getSafeName(name) { 259 | while (has(conflicts, name)) { 260 | name = `_${name}` 261 | } 262 | 263 | conflicts[name] = true 264 | return name 265 | } 266 | } 267 | } 268 | 269 | const emptyArrayPromise = Promise.resolve([]) 270 | 271 | class Module { 272 | constructor({ code, path, bundle }) { 273 | this.code = new MagicString(code, { 274 | filename: path 275 | }) 276 | 277 | this.path = path 278 | this.bundle = bundle 279 | this.suggestedNames = {} 280 | this.ast = parse(code, { 281 | ecmaVersion: 7, 282 | sourceType: 'module', 283 | }) 284 | 285 | this.analyse() 286 | } 287 | 288 | // 分析导入和导出的模块,将引入的模块和导出的模块填入对应的数组 289 | analyse() { 290 | this.imports = {} 291 | this.exports = {} 292 | 293 | this.ast.body.forEach(node => { 294 | let source 295 | 296 | // import foo from './foo' 297 | // import { bar } from './bar' 298 | if (node.type === 'ImportDeclaration') { 299 | source = node.source.value 300 | node.specifiers.forEach(specifier => { 301 | // import foo from './foo' 302 | const isDefault = specifier.type == 'ImportDefaultSpecifier' 303 | // import * as foo from './foo' 304 | const isNamespace = specifier.type == 'ImportNamespaceSpecifier' 305 | 306 | const localName = specifier.local.name 307 | const name = isDefault ? 'default' 308 | : isNamespace ? '*' : specifier.imported.name 309 | 310 | this.imports[localName] = { 311 | source, 312 | name, 313 | localName 314 | } 315 | }) 316 | } else if (/^Export/.test(node.type)) { 317 | // export default function foo () {} 318 | // export default foo 319 | // export default 42 320 | if (node.type === 'ExportDefaultDeclaration') { 321 | const isDeclaration = /Declaration$/.test(node.declaration.type) 322 | this.exports.default = { 323 | node, 324 | name: 'default', 325 | localName: isDeclaration ? node.declaration.id.name : 'default', 326 | isDeclaration 327 | } 328 | } else if (node.type === 'ExportNamedDeclaration') { 329 | // export { foo, bar, baz } 330 | // export var foo = 42 331 | // export function foo () {} 332 | // export { foo } from './foo' 333 | source = node.source && node.source.value 334 | 335 | if (node.specifiers.length) { 336 | // export { foo, bar, baz } 337 | node.specifiers.forEach(specifier => { 338 | const localName = specifier.local.name 339 | const exportedName = specifier.exported.name 340 | 341 | this.exports[exportedName] = { 342 | localName, 343 | exportedName 344 | } 345 | 346 | // export { foo } from './foo' 347 | // 这种格式还需要引入相应的模块,例如上述例子要引入 './foo' 模块 348 | if (source) { 349 | this.imports[localName] = { 350 | source, 351 | localName, 352 | name: exportedName 353 | } 354 | } 355 | }) 356 | } else { 357 | const declaration = node.declaration 358 | let name 359 | 360 | if (declaration.type === 'VariableDeclaration') { 361 | // export var foo = 42 362 | name = declaration.declarations[0].id.name 363 | } else { 364 | // export function foo () {} 365 | name = declaration.id.name 366 | } 367 | 368 | this.exports[name] = { 369 | node, 370 | localName: name, 371 | expression: declaration 372 | } 373 | } 374 | } 375 | } 376 | }) 377 | 378 | // 调用 ast 目录下的 analyse() 379 | analyse(this.ast, this.code, this) 380 | // 当前模块下的顶级变量(包括函数声明) 381 | this.definedNames = this.ast._scope.names.slice() 382 | this.canonicalNames = {} 383 | this.definitions = {} 384 | this.definitionPromises = {} 385 | this.modifications = {} 386 | 387 | this.ast.body.forEach(statement => { 388 | // 读取当前语句下的变量 389 | Object.keys(statement._defines).forEach(name => { 390 | this.definitions[name] = statement 391 | }) 392 | 393 | // 再根据 _modifies 修改它们,_modifies 是在 analyse() 中改变的 394 | Object.keys(statement._modifies).forEach(name => { 395 | if (!has(this.modifications, name)) { 396 | this.modifications[name] = [] 397 | } 398 | 399 | this.modifications[name].push(statement) 400 | }) 401 | }) 402 | } 403 | 404 | expandAllStatements(isEntryModule) { 405 | let allStatements = [] 406 | 407 | return sequence(this.ast.body, statement => { 408 | // skip already-included statements 409 | if (statement._included) return 410 | 411 | // 不需要对导入语句作处理 412 | if (statement.type === 'ImportDeclaration') { 413 | return 414 | } 415 | 416 | // skip `export { foo, bar, baz }` 417 | if (statement.type === 'ExportNamedDeclaration' && statement.specifiers.length) { 418 | // but ensure they are defined, if this is the entry module 419 | // export { foo, bar, baz } 420 | // 遇到这样的语句,如果是从其他模块引入的函数,则会去对应的模块加载函数, 421 | if (isEntryModule) { 422 | return this.expandStatement(statement) 423 | .then(statements => { 424 | allStatements.push.apply(allStatements, statements) 425 | }) 426 | } 427 | 428 | return 429 | } 430 | 431 | // 剩下的其他类型语句则要添加到 allStatements 中,以待在 bundle.generate() 中生成 432 | // include everything else 433 | return this.expandStatement(statement) 434 | .then(statements => { 435 | allStatements.push.apply(allStatements, statements) 436 | }) 437 | }).then(() => { 438 | return allStatements 439 | }) 440 | } 441 | 442 | expandStatement(statement) { 443 | if (statement._included) return emptyArrayPromise 444 | statement._included = true 445 | 446 | let result = [] 447 | 448 | // 根据 AST 节点的依赖项找到相应的模块 449 | // 例如依赖 path 模块,就需要去找到它 450 | const dependencies = Object.keys(statement._dependsOn) 451 | 452 | return sequence(dependencies, name => { 453 | // define() 将从其他模块中引入的函数加载进来 454 | return this.define(name).then(definition => { 455 | result.push.apply(result, definition) 456 | }) 457 | }) 458 | 459 | // then include the statement itself 460 | .then(() => { 461 | result.push(statement) 462 | }) 463 | .then(() => { 464 | // then include any statements that could modify the 465 | // thing(s) this statement defines 466 | return sequence(keys(statement._defines), name => { 467 | const modifications = has(this.modifications, name) && this.modifications[name] 468 | 469 | if (modifications) { 470 | return sequence(modifications, statement => { 471 | if (!statement._included) { 472 | return this.expandStatement(statement) 473 | .then(statements => { 474 | result.push.apply(result, statements) 475 | }) 476 | } 477 | }) 478 | } 479 | }) 480 | }) 481 | .then(() => { 482 | // the `result` is an array of statements needed to define `name` 483 | return result 484 | }) 485 | } 486 | 487 | define(name) { 488 | if (has(this.definitionPromises, name)) { 489 | return emptyArrayPromise 490 | } 491 | 492 | let promise 493 | 494 | // The definition for this name is in a different module 495 | if (has(this.imports, name)) { 496 | const importDeclaration = this.imports[name] 497 | 498 | promise = this.bundle.fetchModule(importDeclaration.source, this.path) 499 | .then(module => { 500 | importDeclaration.module = module 501 | 502 | // suggest names. TODO should this apply to non default/* imports? 503 | if (importDeclaration.name === 'default') { 504 | // TODO this seems ropey 505 | const localName = importDeclaration.localName 506 | const suggestion = has(this.suggestedNames, localName) ? this.suggestedNames[localName] : localName 507 | module.suggestName('default', suggestion) 508 | } else if (importDeclaration.name === '*') { 509 | const localName = importDeclaration.localName 510 | const suggestion = has(this.suggestedNames, localName) ? this.suggestedNames[localName] : localName 511 | module.suggestName('*', suggestion) 512 | module.suggestName('default', `${suggestion}__default`) 513 | } 514 | 515 | if (module.isExternal) { 516 | if (importDeclaration.name === 'default') { 517 | module.needsDefault = true 518 | } else { 519 | module.needsNamed = true 520 | } 521 | 522 | module.importedByBundle.push(importDeclaration) 523 | return emptyArrayPromise 524 | } 525 | 526 | if (importDeclaration.name === '*') { 527 | // we need to create an internal namespace 528 | if (!this.bundle.internalNamespaceModules.includes(module)) { 529 | this.bundle.internalNamespaceModules.push(module) 530 | } 531 | 532 | return module.expandAllStatements() 533 | } 534 | 535 | const exportDeclaration = module.exports[importDeclaration.name] 536 | 537 | if (!exportDeclaration) { 538 | throw new Error(`Module ${module.path} does not export ${importDeclaration.name} (imported by ${this.path})`) 539 | } 540 | 541 | return module.define(exportDeclaration.localName) 542 | }) 543 | } 544 | // The definition is in this module 545 | else if (name === 'default' && this.exports.default.isDeclaration) { 546 | // We have something like `export default foo` - so we just start again, 547 | // searching for `foo` instead of default 548 | promise = this.define(this.exports.default.name) 549 | } else { 550 | let statement 551 | 552 | if (name === 'default') { 553 | // TODO can we use this.definitions[name], as below? 554 | statement = this.exports.default.node 555 | } else { 556 | statement = this.definitions[name] 557 | } 558 | 559 | if (statement && !statement._included) { 560 | promise = this.expandStatement(statement) 561 | } 562 | } 563 | 564 | this.definitionPromises[name] = promise || emptyArrayPromise 565 | return this.definitionPromises[name] 566 | } 567 | 568 | getCanonicalName(localName) { 569 | if (has(this.suggestedNames, localName)) { 570 | localName = this.suggestedNames[localName] 571 | } 572 | 573 | if (!has(this.canonicalNames, localName)) { 574 | let canonicalName 575 | 576 | if (has(this.imports, localName)) { 577 | const importDeclaration = this.imports[localName] 578 | const module = importDeclaration.module 579 | 580 | if (importDeclaration.name === '*') { 581 | canonicalName = module.suggestedNames['*'] 582 | } else { 583 | let exporterLocalName 584 | 585 | if (module.isExternal) { 586 | exporterLocalName = importDeclaration.name 587 | } else { 588 | const exportDeclaration = module.exports[importDeclaration.name] 589 | exporterLocalName = exportDeclaration.localName 590 | } 591 | 592 | canonicalName = module.getCanonicalName(exporterLocalName) 593 | } 594 | } else { 595 | canonicalName = localName 596 | } 597 | 598 | this.canonicalNames[localName] = canonicalName 599 | } 600 | 601 | return this.canonicalNames[localName] 602 | } 603 | 604 | rename(name, replacement) { 605 | this.canonicalNames[name] = replacement 606 | } 607 | 608 | suggestName(exportName, suggestion) { 609 | if (!this.suggestedNames[exportName]) { 610 | this.suggestedNames[exportName] = suggestion 611 | } 612 | } 613 | } 614 | 615 | class ExternalModule { 616 | constructor(id) { 617 | this.id = id 618 | this.name = null 619 | 620 | this.isExternal = true 621 | this.importedByBundle = [] 622 | 623 | this.canonicalNames = {} 624 | this.suggestedNames = {} 625 | 626 | this.needsDefault = false 627 | this.needsNamed = false 628 | } 629 | 630 | getCanonicalName(name) { 631 | if (name === 'default') { 632 | return this.needsNamed ? `${this.name}__default` : this.name 633 | } 634 | 635 | if (name === '*') { 636 | return this.name 637 | } 638 | 639 | // TODO this depends on the output format... works for CJS etc but not ES6 640 | return `${this.name}.${name}` 641 | } 642 | 643 | rename(name, replacement) { 644 | this.canonicalNames[name] = replacement 645 | } 646 | 647 | suggestName(exportName, suggestion) { 648 | if (!this.suggestedNames[exportName]) { 649 | this.suggestedNames[exportName] = suggestion 650 | } 651 | } 652 | } 653 | 654 | function getName(x) { 655 | return x.name 656 | } 657 | 658 | const keys = Object.keys 659 | 660 | const hasOwnProp = Object.prototype.hasOwnProperty 661 | 662 | function has(obj, prop) { 663 | return hasOwnProp.call(obj, prop) 664 | } 665 | 666 | // 将数组每一项当成参数传给 callback 执行,最后将结果用 promise 返回 667 | function sequence (arr, callback) { 668 | const len = arr.length 669 | const results = new Array(len) 670 | let promise = Promise.resolve() 671 | 672 | function next(i) { 673 | return promise 674 | .then(() => callback(arr[i], i)) 675 | .then(result => results[i] = result) 676 | } 677 | 678 | let i 679 | for (i = 0; i < len; i += 1) { 680 | promise = next(i) 681 | } 682 | 683 | return promise.then(() => results) 684 | } 685 | 686 | // 重写 node 名称 687 | // 例如 import { resolve } from path; 将 resolve 变为 path.resolve 688 | function replaceIdentifiers(statement, snippet, names) { 689 | const replacementStack = [names] 690 | const keys = Object.keys(names) 691 | 692 | if (keys.length === 0) { 693 | return 694 | } 695 | 696 | walk(statement, { 697 | enter(node, parent) { 698 | const scope = node._scope 699 | 700 | if (scope) { 701 | let newNames = {} 702 | let hasReplacements 703 | 704 | keys.forEach(key => { 705 | if (!scope.names.includes(key)) { 706 | newNames[key] = names[key] 707 | hasReplacements = true 708 | } 709 | }) 710 | 711 | if (!hasReplacements) { 712 | return this.skip() 713 | } 714 | 715 | names = newNames 716 | replacementStack.push(newNames) 717 | } 718 | 719 | // We want to rewrite identifiers (that aren't property names) 720 | if (node.type !== 'Identifier') return 721 | if (parent.type === 'MemberExpression' && node !== parent.object) return 722 | if (parent.type === 'Property' && node !== parent.value) return 723 | 724 | const name = has(names, node.name) && names[node.name] 725 | 726 | if (name && name !== node.name) { 727 | snippet.overwrite(node.start, node.end, name) 728 | } 729 | }, 730 | 731 | leave(node) { 732 | if (node._scope) { 733 | replacementStack.pop() 734 | names = replacementStack[replacementStack.length - 1] 735 | } 736 | } 737 | }) 738 | } 739 | 740 | function cjs(bundle, magicString, exportMode) { 741 | let intro = `'use strict'\n\n` 742 | 743 | const importBlock = bundle.externalModules 744 | .map(module => { 745 | let requireStatement = `var ${module.name} = require('${module.id}')` 746 | 747 | if (module.needsDefault) { 748 | requireStatement += '\n' + (module.needsNamed ? `var ${module.name}__default = ` : `${module.name} = `) + 749 | `'default' in ${module.name} ? ${module.name}['default'] : ${module.name}` 750 | } 751 | 752 | return requireStatement 753 | }) 754 | .join('\n') 755 | 756 | if (importBlock) { 757 | intro += importBlock + '\n\n' 758 | } 759 | 760 | magicString.prepend(intro) 761 | 762 | let exportBlock 763 | if (exportMode === 'default' && bundle.entryModule.exports.default) { 764 | exportBlock = `module.exports = ${bundle.entryModule.getCanonicalName('default')}` 765 | } else if (exportMode === 'named') { 766 | exportBlock = keys(bundle.entryModule.exports) 767 | .map(key => { 768 | const specifier = bundle.entryModule.exports[key] 769 | const name = bundle.entryModule.getCanonicalName(specifier.localName) 770 | 771 | return `exports.${key} = ${name}` 772 | }) 773 | .join('\n') 774 | } 775 | 776 | if (exportBlock) { 777 | magicString.append('\n\n' + exportBlock) 778 | } 779 | 780 | return magicString 781 | } 782 | 783 | // 对 AST 进行分析,按节点层级赋予对应的作用域,并找出有哪些依赖项和对依赖项作了哪些修改 784 | function analyse(ast, magicString, module) { 785 | let scope = new Scope() 786 | let currentTopLevelStatement 787 | 788 | function addToScope(declarator) { 789 | var name = declarator.id.name 790 | scope.add(name, false) 791 | 792 | if (!scope.parent) { 793 | currentTopLevelStatement._defines[name] = true 794 | } 795 | } 796 | 797 | function addToBlockScope(declarator) { 798 | var name = declarator.id.name 799 | scope.add(name, true) 800 | 801 | if (!scope.parent) { 802 | currentTopLevelStatement._defines[name] = true 803 | } 804 | } 805 | 806 | // first we need to generate comprehensive scope info 807 | let previousStatement = null 808 | 809 | // 为每个语句定义作用域,并将父子作用域关联起来 810 | ast.body.forEach(statement => { 811 | currentTopLevelStatement = statement // so we can attach scoping info 812 | 813 | // 这些属性不能遍历 814 | Object.defineProperties(statement, { 815 | _defines: { value: {} }, 816 | _modifies: { value: {} }, 817 | _dependsOn: { value: {} }, 818 | _included: { value: false, writable: true }, 819 | _module: { value: module }, 820 | _source: { value: magicString.snip(statement.start, statement.end) }, 821 | _margin: { value: [0, 0] }, 822 | }) 823 | 824 | // determine margin 825 | const previousEnd = previousStatement ? previousStatement.end : 0 826 | const start = statement.start 827 | 828 | const gap = magicString.original.slice(previousEnd, start) 829 | const margin = gap.split('\n').length 830 | 831 | if (previousStatement) previousStatement._margin[1] = margin 832 | statement._margin[0] = margin 833 | 834 | walk(statement, { 835 | enter (node) { 836 | let newScope 837 | switch (node.type) { 838 | case 'FunctionExpression': 839 | case 'FunctionDeclaration': 840 | case 'ArrowFunctionExpression': 841 | const names = node.params.map(getName) 842 | 843 | if (node.type === 'FunctionDeclaration') { 844 | addToScope(node) 845 | } else if (node.type === 'FunctionExpression' && node.id) { 846 | names.push(node.id.name) 847 | } 848 | 849 | newScope = new Scope({ 850 | parent: scope, 851 | params: names, // TODO rest params? 852 | block: false 853 | }) 854 | 855 | break 856 | 857 | case 'BlockStatement': 858 | newScope = new Scope({ 859 | parent: scope, 860 | block: true 861 | }) 862 | 863 | break 864 | 865 | case 'CatchClause': 866 | newScope = new Scope({ 867 | parent: scope, 868 | params: [node.param.name], 869 | block: true 870 | }) 871 | 872 | break 873 | 874 | case 'VariableDeclaration': 875 | node.declarations.forEach(node.kind === 'let' ? addToBlockScope : addToScope) // TODO const? 876 | break 877 | 878 | case 'ClassDeclaration': 879 | addToScope(node) 880 | break 881 | } 882 | 883 | if (newScope) { 884 | Object.defineProperty(node, '_scope', { value: newScope }) 885 | scope = newScope 886 | } 887 | }, 888 | leave (node) { 889 | if (node === currentTopLevelStatement) { 890 | currentTopLevelStatement = null 891 | } 892 | 893 | if (node._scope) { 894 | scope = scope.parent 895 | } 896 | } 897 | }) 898 | 899 | previousStatement = statement 900 | }) 901 | 902 | // then, we need to find which top-level dependencies this statement has, 903 | // and which it potentially modifies 904 | // 然后,我们需要找出这个语句有哪些顶级依赖项,以及它可能修改哪些依赖项 905 | ast.body.forEach(statement => { 906 | function checkForReads (node, parent) { 907 | // 节点类型为 Identifier,并且不存在 statement 作用域中,说明它是顶级依赖项 908 | if (node.type === 'Identifier') { 909 | // disregard the `bar` in `foo.bar` - these appear as Identifier nodes 910 | if (parent.type === 'MemberExpression' && node !== parent.object) { 911 | return 912 | } 913 | 914 | // disregard the `bar` in { bar: foo } 915 | if (parent.type === 'Property' && node !== parent.value) { 916 | return 917 | } 918 | 919 | const definingScope = scope.findDefiningScope(node.name) 920 | 921 | if ((!definingScope || definingScope.depth === 0) && !statement._defines[node.name]) { 922 | statement._dependsOn[node.name] = true 923 | } 924 | } 925 | 926 | } 927 | // 检查有没修改依赖 928 | function checkForWrites(node) { 929 | function addNode (node, disallowImportReassignments) { 930 | while (node.type === 'MemberExpression') { 931 | node = node.object 932 | } 933 | 934 | if (node.type !== 'Identifier') { 935 | return 936 | } 937 | 938 | statement._modifies[node.name] = true 939 | } 940 | 941 | // 检查 a = 1 + 2 中的 a 是否被修改 942 | // 如果 a 是引入模块并且被修改就报错 943 | if (node.type === 'AssignmentExpression') { 944 | addNode(node.left, true) 945 | } 946 | // a++/a-- 947 | else if (node.type === 'UpdateExpression') { 948 | addNode(node.argument, true) 949 | } else if (node.type === 'CallExpression') { 950 | node.arguments.forEach(arg => addNode(arg, false)) 951 | } 952 | } 953 | 954 | walk(statement, { 955 | enter (node, parent) { 956 | // skip imports 957 | if (/^Import/.test(node.type)) return this.skip() 958 | 959 | if (node._scope) scope = node._scope 960 | 961 | checkForReads(node, parent) 962 | checkForWrites(node, parent) 963 | }, 964 | leave (node) { 965 | if (node._scope) scope = scope.parent 966 | } 967 | }) 968 | }) 969 | 970 | ast._scope = scope 971 | } 972 | 973 | // 作用域 974 | class Scope { 975 | constructor(options = {}) { 976 | this.parent = options.parent 977 | this.depth = this.parent ? this.parent.depth + 1 : 0 978 | this.names = options.params || [] 979 | this.isBlockScope = !!options.block 980 | } 981 | 982 | add(name, isBlockDeclaration) { 983 | if (!isBlockDeclaration && this.isBlockScope) { 984 | // it's a `var` or function declaration, and this 985 | // is a block scope, so we need to go up 986 | this.parent.add(name, isBlockDeclaration) 987 | } else { 988 | this.names.push(name) 989 | } 990 | } 991 | 992 | contains(name) { 993 | return !!this.findDefiningScope(name) 994 | } 995 | 996 | findDefiningScope(name) { 997 | if (this.names.includes(name)) { 998 | return this 999 | } 1000 | 1001 | if (this.parent) { 1002 | return this.parent.findDefiningScope(name) 1003 | } 1004 | 1005 | return null 1006 | } 1007 | } 1008 | 1009 | let shouldSkip 1010 | let shouldAbort 1011 | // 对 AST 的节点调用 enter() 和 leave() 函数,如果有子节点将递归调用 1012 | function walk (ast, { enter, leave }) { 1013 | shouldAbort = false 1014 | visit(ast, null, enter, leave) 1015 | } 1016 | 1017 | let context = { 1018 | skip: () => shouldSkip = true, 1019 | abort: () => shouldAbort = true 1020 | } 1021 | 1022 | let childKeys = {} 1023 | 1024 | let toString = Object.prototype.toString 1025 | 1026 | function isArray (thing) { 1027 | return toString.call(thing) === '[object Array]' 1028 | } 1029 | 1030 | function visit (node, parent, enter, leave) { 1031 | if (!node || shouldAbort) return 1032 | 1033 | if (enter) { 1034 | shouldSkip = false 1035 | enter.call(context, node, parent) 1036 | if (shouldSkip || shouldAbort) return 1037 | } 1038 | 1039 | let keys = childKeys[node.type] || ( 1040 | childKeys[node.type] = Object.keys(node).filter(key => typeof node[key] === 'object') 1041 | ) 1042 | 1043 | let key, value, i, j 1044 | 1045 | i = keys.length 1046 | while (i--) { 1047 | key = keys[i] 1048 | value = node[key] 1049 | 1050 | if (isArray(value)) { 1051 | j = value.length 1052 | while (j--) { 1053 | visit(value[j], node, enter, leave) 1054 | } 1055 | } 1056 | 1057 | else if (value && value.type) { 1058 | visit(value, node, enter, leave) 1059 | } 1060 | } 1061 | 1062 | if (leave && !shouldAbort) { 1063 | leave(node, parent) 1064 | } 1065 | } 1066 | 1067 | module.exports = rollup --------------------------------------------------------------------------------