-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement tree-shaking plugin as
interlock-dce
.
Closes #11.
- Loading branch information
Showing
8 changed files
with
189 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
src/ | ||
example/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
# interlock-dce |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { foo, biz, default as def } from "./lib"; | ||
|
||
console.log("foo", foo); | ||
console.log("biz", biz("msg")); | ||
console.log("default", def()); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
export const foo = "foo"; | ||
|
||
export function bar () { | ||
return "bar-bar-bar"; | ||
} | ||
|
||
export function baz (msg) { | ||
return "baz-baz-baz " + msg; | ||
} | ||
|
||
export const biz = function (msg) { | ||
return "biz-biz-biz " + baz(msg); | ||
}; | ||
|
||
export default function () { | ||
return "default"; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
var path = require("path"); | ||
|
||
var Interlock = require("interlock"); | ||
var dce = require(".."); | ||
|
||
var ilk = new Interlock({ | ||
srcRoot: __dirname, | ||
destRoot: path.join(__dirname, "dist"), | ||
entry: { "./app/app.js": "app.bundle.js" }, | ||
pretty: true, | ||
plugins: [ dce() ] | ||
}); | ||
|
||
ilk.build(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
{ | ||
"name": "interlock-dce-example", | ||
"version": "0.0.1", | ||
"description": "", | ||
"main": "app/example.js", | ||
"scripts": { | ||
"test": "echo \"Error: no test specified\" && exit 1" | ||
}, | ||
"author": "Dale Bustad <[email protected]>", | ||
"license": "MIT" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
{ | ||
"name": "interlock-dce", | ||
"version": "0.1.0", | ||
"description": "", | ||
"main": "lib/index.js", | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/interlockjs/plugins.git" | ||
}, | ||
"author": "Dale Bustad <[email protected]>", | ||
"license": "MIT", | ||
"bugs": { | ||
"url": "https://github.com/interlockjs/plugins/issues" | ||
}, | ||
"homepage": "https://github.com/interlockjs/plugins/packages/dce/#readme", | ||
"dependencies": { | ||
"babel-traverse": "^6.7.4" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
import traverse from "babel-traverse"; | ||
|
||
|
||
export default function (/* opts = {} */) { | ||
const metadata = {}; | ||
|
||
return (override, transform) => { | ||
|
||
/** | ||
* Record all ES6 exports and imports for each module. | ||
* | ||
* When an export is found, create a corresponding property in the | ||
* module's `exports` object, set to `false`. These will later be | ||
* toggled to `true` for exports that are imported. | ||
* | ||
* When an import is found, create a corresponding property for the | ||
* _source_ module (whatever is in the import source string) in the | ||
* module's `imports` object. The value of this property will be | ||
* an array containing all imported identifiers. | ||
*/ | ||
transform("parseModule", module => { | ||
const moduleMetadata = metadata[module.path] = { exports: {}, imports: {} }; | ||
|
||
traverse.cheap(module.ast, node => { | ||
// Look for named exports. | ||
if (node.type === "ExportNamedDeclaration") { | ||
if (node.declaration && node.declaration.type === "FunctionDeclaration") { | ||
moduleMetadata.exports[node.declaration.id.name] = false; | ||
} else if (node.declaration && node.declaration.type === "VariableDeclaration") { | ||
node.declaration.declarations.forEach(decl => { | ||
moduleMetadata.exports[decl.id.name] = false; | ||
}); | ||
} | ||
|
||
// Look for default export. | ||
} else if (node.type === "ExportDefaultDeclaration") { | ||
moduleMetadata.exports.default = false; | ||
|
||
// Look for imports. | ||
} else if (node.type === "ImportDeclaration") { | ||
const imports = moduleMetadata.imports[node.source.value] = | ||
moduleMetadata.imports[node.source.value] || []; | ||
|
||
node.specifiers.forEach(specifier => { | ||
imports.push(specifier.imported.name); | ||
}); | ||
|
||
// Look for default imports. | ||
} else if (node.type === "ImportDefaultDeclaration") { | ||
const imports = moduleMetadata.imports[node.source.value] = | ||
moduleMetadata.imports[node.source.value] || []; | ||
imports.push("default"); | ||
} | ||
}); | ||
|
||
return module; | ||
}); | ||
|
||
/** | ||
* First, iterate over each module's internal references (any import | ||
* sources or require() arguments). Then, find the corresponding export | ||
* metadata for that dependency, and mark its exports as used if | ||
* they are imported in the current module. | ||
* | ||
* After this has happened for all modules, iterate over each module | ||
* again. This time remove `exports.foo` for any "foo" that was unused. | ||
* | ||
* Any function/variable declarations that are 1) unexported, and | ||
* 2) unused elsewhere in the module, will be stripped out by a minifier. | ||
*/ | ||
transform("compileModules", modules => { | ||
// Mark used exports. | ||
modules.forEach(module => { | ||
const moduleMetadata = metadata[module.path]; | ||
|
||
// Iterate over all dependencies. | ||
Object.keys(module.dependenciesByInternalRef).forEach(internalRef => { | ||
const dep = module.dependenciesByInternalRef[internalRef]; | ||
const depMetadata = metadata[dep.path]; | ||
|
||
// Iterate over all identifiers imported from this dependency, marking | ||
// _its_ export as used. | ||
moduleMetadata.imports[internalRef].forEach(identifier => { | ||
depMetadata.exports[identifier] = true; | ||
}); | ||
}); | ||
}); | ||
|
||
// Remove `exports.foo` for any unused exports. | ||
modules.forEach(module => { | ||
const shouldExport = metadata[module.path].exports; | ||
|
||
traverse(module.ast, { | ||
noScope: true, | ||
enter: path => { | ||
if ( | ||
path.node.type === "AssignmentExpression" && | ||
path.node.left.type === "MemberExpression" && | ||
path.node.left.object.name === "exports" | ||
) { | ||
if (!shouldExport[path.node.left.property.name]) { | ||
// const thing = exports.thing = "thing"; | ||
// --> const thign = "thing"; | ||
// exports.foo = function () { /* ... */ }; | ||
// --> foo; | ||
// --> function foo () { /* ... */ }; | ||
// exports.default = "default"; | ||
// --> "default"; | ||
// | ||
path.replaceWith(path.node.right); | ||
} | ||
} | ||
} | ||
}); | ||
}); | ||
|
||
return modules; | ||
}); | ||
}; | ||
} |