Skip to content

Commit

Permalink
Determine used exports for CJS modules
Browse files Browse the repository at this point in the history
Based on the entire graph, not just the subset of the graph that has
already loaded.
  • Loading branch information
matthewp committed Nov 27, 2018
1 parent f8ecfd4 commit 4a139ab
Show file tree
Hide file tree
Showing 10 changed files with 118 additions and 27 deletions.
34 changes: 34 additions & 0 deletions lib/graph/each_dependants.js
@@ -0,0 +1,34 @@
var isString = require("lodash/isString");

/**
* @method graph.eachDependants
*/
module.exports = function(graph, names, callback){
var deps = {};
var nodeNames = isString(names) ? [names] : names;

function visit(name, node) {
if(!deps[name]) {
deps[name] = true;

if(node && node.dependencies.length) {
if(includes(node.dependencies, nodeNames)) {
callback(name, node);
}
}
}
}

for(var name in graph) {
visit(name, graph[name]);
}
};

function includes(arr1, arr2) {
for(var i = 0; i < arr2.length; i++) {
if(arr1.indexOf(arr2[i]) !== -1) {
return true;
}
}
return false;
}
9 changes: 9 additions & 0 deletions lib/graph/get_dependants.js
@@ -0,0 +1,9 @@
var eachDependants = require("./each_dependants");

module.exports = function(graph, name) {
var deps = [];
eachDependants(graph, name, function(depName) {
deps.push(depName);
});
return deps;
};
37 changes: 11 additions & 26 deletions lib/graph/treeshake.js
Expand Up @@ -4,6 +4,7 @@ var os = require("os");
var babel = require("babel-standalone");
var entries = require("object.entries");
var dependencyResolver = require("../node/dependency_resolver");
var getDependants = require("./get_dependants");
var processBabelPlugins = require("../process_babel_plugins");
var processBabelPresets = require("../process_babel_presets");
var rollup = require("steal-rollup");
Expand Down Expand Up @@ -31,7 +32,7 @@ function treeshake(data) {
experimentalPreserveModules: true,
experimentalCodeSplitting: true,
plugins: [
loadFromGraph(getNode),
loadFromGraph(getNode, data),
transpile(getNode, data)
],
onwarn: function(){}
Expand Down Expand Up @@ -75,7 +76,7 @@ function treeshake(data) {
});
}

function loadFromGraph(getNode) {
function loadFromGraph(getNode, data) {
return {
resolveId: function(id, importer) {
if(importer) {
Expand All @@ -90,25 +91,23 @@ function loadFromGraph(getNode) {

if(notESModule(node)) {
let needToExport = new Set();
let dependants = (node && node.load.metadata.dependants) || [];
let dependants = (node && getDependants(data.graph, node.load.name)) || [];

// Determine what to export by looking at dependants imports
for(let depName of dependants) {
let localNode = getNode(depName);
let subgraph = localNode.load.metadata.subgraph;
if(!subgraph) {
continue;
}
let specifiers = localNode.load.metadata.importSpecifiers || {};

for(let imp of subgraph.imports) {
let depName = moduleNameFromSpecifier(localNode, imp.source);
for(let [impSource,] of entries(specifiers)) {
let depName = moduleNameFromSpecifier(localNode, impSource);
if(depName !== id) {
continue;
}

for(let spec of imp.specifiers) {
needToExport.add(spec.imported);
}
let imported = localNode.load.metadata.importNames[impSource] || [];
imported.forEach(imported => {
needToExport.add(imported);
});
}
}

Expand Down Expand Up @@ -214,20 +213,6 @@ function transpile(getNode, data) {

let result = babel.transform(code, opts);

for(let depName of node.load.metadata.dependencies) {
let localNode = getNode(depName);
if(!localNode) {
continue;
}
if(!localNode.load.metadata.dependants) {
localNode.load.metadata.dependants = [];
}
localNode.load.metadata.dependants.push(id);
}

// Update metadata with specifiers
node.load.metadata.subgraph = result.metadata.modules;

return {
code: result.code,
map: result.map
Expand Down
38 changes: 37 additions & 1 deletion test/tree_shaking_test.js
Expand Up @@ -271,5 +271,41 @@ describe("Tree-shaking", function(){
assert.equal(app.a, "a", "main bundle loaded");
assert.equal(app.b, "b", "this bundle loaded");
});
})
});

describe("import { dep } from 'cjs'", function() {
before(function(done){
this.timeout(20000);
var base = path.join(__dirname, "treeshake", "cjs");
var config = { config: path.join(base, "package.json!npm") };
var page = `prod.html`;

rmdir(path.join(base, "dist"))
.then(function() {
return build(config, {
quiet: true,
minify: false
});
})
.then(function() {
var close;
return open(path.join("test", "treeshake", "cjs", page))
.then(function(args) {
close = args.close;
browser = args.browser;
return find(browser, "APP");
})
.then(function(mod) {
app = mod;
close();
done();
});
})
.catch(done);
});

it("Used imports are available", function() {
assert.equal(typeof app.assign, "function", "got the exported value");
});
});
});
6 changes: 6 additions & 0 deletions test/treeshake/cjs/dep.js
@@ -0,0 +1,6 @@

exports.pick = function(){};

exports.assign = function(){};

exports.isUndefined = function(){};
4 changes: 4 additions & 0 deletions test/treeshake/cjs/index.js
@@ -0,0 +1,4 @@
import { assign } from './dep';
import './other';

window.APP = { assign };
4 changes: 4 additions & 0 deletions test/treeshake/cjs/other.js
@@ -0,0 +1,4 @@
import { pick } from './dep';

window.APP = window.APP || {};
window.APP.pick = pick;
8 changes: 8 additions & 0 deletions test/treeshake/cjs/package.json
@@ -0,0 +1,8 @@
{
"name": "app",
"main": "index.js",
"version": "1.0.0",
"steal": {
"bundle": ["~/page"]
}
}
4 changes: 4 additions & 0 deletions test/treeshake/cjs/page.js
@@ -0,0 +1,4 @@
import { isUndefined } from './dep';

window.APP = window.APP || {};
window.APP.isUndefined = isUndefined;
1 change: 1 addition & 0 deletions test/treeshake/cjs/prod.html
@@ -0,0 +1 @@
<script src="./dist/steal.production.js"></script>

0 comments on commit 4a139ab

Please sign in to comment.