Skip to content

Commit

Permalink
add support for tree-shaking JSON modules
Browse files Browse the repository at this point in the history
  • Loading branch information
sokra committed Nov 5, 2019
1 parent 2ee42bd commit 14ee25c
Show file tree
Hide file tree
Showing 15 changed files with 438 additions and 127 deletions.
1 change: 1 addition & 0 deletions lib/Dependency.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
* @typedef {Object} ExportSpec
* @property {string} name the name of the export
* @property {boolean=} canMangle can the export be renamed (defaults to true)
* @property {(string | ExportSpec)[]=} exports nested exports
* @property {Module=} from when reexported: from which module
* @property {string[] | null=} export when reexported: from which export
*/
Expand Down
70 changes: 40 additions & 30 deletions lib/FlagDependencyExportsPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,43 +115,53 @@ class FlagDependencyExportsPlugin {
}
} else if (Array.isArray(exports)) {
// merge in new exports
for (const exportNameOrSpec of exports) {
if (typeof exportNameOrSpec === "string") {
const exportInfo = exportsInfo.getExportInfo(
exportNameOrSpec
);
if (exportInfo.provided === false) {
exportInfo.provided = true;
changed = true;
}
} else {
const exportInfo = exportsInfo.getExportInfo(
exportNameOrSpec.name
);
if (exportInfo.provided === false) {
exportInfo.provided = true;
changed = true;
}
if (exportNameOrSpec.canMangle === false) {
if (exportInfo.canMangleProvide !== false) {
exportInfo.canMangleProvide = false;
const mergeExports = (exportsInfo, exports) => {
for (const exportNameOrSpec of exports) {
if (typeof exportNameOrSpec === "string") {
const exportInfo = exportsInfo.getExportInfo(
exportNameOrSpec
);
if (exportInfo.provided === false) {
exportInfo.provided = true;
changed = true;
}
}
if (exportNameOrSpec.from) {
const fromExportsInfo = moduleGraph.getExportsInfo(
exportNameOrSpec.from
} else {
const exportInfo = exportsInfo.getExportInfo(
exportNameOrSpec.name
);
const nestedExportsInfo = fromExportsInfo.getNestedExportsInfo(
exportNameOrSpec.export
);
if (!exportInfo.exportsInfo && nestedExportsInfo) {
exportInfo.exportsInfo = nestedExportsInfo;
if (exportInfo.provided === false) {
exportInfo.provided = true;
changed = true;
}
if (exportNameOrSpec.canMangle === false) {
if (exportInfo.canMangleProvide !== false) {
exportInfo.canMangleProvide = false;
changed = true;
}
}
if (exportNameOrSpec.exports) {
const nestedExportsInfo = exportInfo.createNestedExportsInfo();
mergeExports(
nestedExportsInfo,
exportNameOrSpec.exports
);
}
if (exportNameOrSpec.from) {
const fromExportsInfo = moduleGraph.getExportsInfo(
exportNameOrSpec.from
);
const nestedExportsInfo = fromExportsInfo.getNestedExportsInfo(
exportNameOrSpec.export
);
if (!exportInfo.exportsInfo && nestedExportsInfo) {
exportInfo.exportsInfo = nestedExportsInfo;
changed = true;
}
}
}
}
}
};
mergeExports(exportsInfo, exports);
}
// store dependencies
if (exportDeps) {
Expand Down
59 changes: 29 additions & 30 deletions lib/FlagDependencyUsagePlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,41 +42,28 @@ class FlagDependencyUsagePlugin {
const processModule = (module, usedExports) => {
const exportsInfo = moduleGraph.getExportsInfo(module);
if (usedExports.length > 0) {
for (const usedExport of usedExports) {
for (let usedExport of usedExports) {
if (
module.buildMeta.exportsType === "named" &&
usedExport[0] === "default"
) {
usedExport = usedExport.slice(1);
}
if (usedExport.length === 0) {
if (exportsInfo.setUsedInUnknownWay()) {
queue.enqueue(module);
}
} else {
if (
usedExport[0] === "default" &&
module.buildMeta.exportsType === "named"
) {
if (exportsInfo.setUsedAsNamedExportType()) {
queue.enqueue(module);
}
} else {
let currentExportsInfo = exportsInfo;
for (let i = 0; i < usedExport.length; i++) {
const exportName = usedExport[i];
const exportInfo = currentExportsInfo.getExportInfo(
exportName
);
const lastOne = i === usedExport.length - 1;
const nestedInfo = exportInfo.exportsInfo;
if (!nestedInfo || lastOne) {
if (exportInfo.used !== UsageState.Used) {
exportInfo.used = UsageState.Used;
const currentModule =
currentExportsInfo === exportsInfo
? module
: exportInfoToModuleMap.get(currentExportsInfo);
if (currentModule) {
queue.enqueue(currentModule);
}
}
break;
} else {
let currentExportsInfo = exportsInfo;
for (let i = 0; i < usedExport.length; i++) {
const exportName = usedExport[i];
const exportInfo = currentExportsInfo.getExportInfo(
exportName
);
const lastOne = i === usedExport.length - 1;
if (!lastOne) {
const nestedInfo = exportInfo.getNestedExportsInfo();
if (nestedInfo) {
if (exportInfo.used === UsageState.Unused) {
exportInfo.used = UsageState.OnlyPropertiesUsed;
const currentModule =
Expand All @@ -88,8 +75,20 @@ class FlagDependencyUsagePlugin {
}
}
currentExportsInfo = nestedInfo;
continue;
}
}
if (exportInfo.used !== UsageState.Used) {
exportInfo.used = UsageState.Used;
const currentModule =
currentExportsInfo === exportsInfo
? module
: exportInfoToModuleMap.get(currentExportsInfo);
if (currentModule) {
queue.enqueue(currentModule);
}
}
break;
}
}
}
Expand Down
17 changes: 17 additions & 0 deletions lib/ModuleGraph.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ class ExportsInfo {
if (exportInfo.canMangleUse === undefined) {
exportInfo.canMangleUse = true;
}
if (exportInfo.exportsInfoOwned) {
exportInfo.exportsInfo.setHasUseInfo();
}
}
if (this._otherExportsInfo.used === UsageState.NoInfo) {
this._otherExportsInfo.used = UsageState.Unused;
Expand Down Expand Up @@ -481,6 +484,8 @@ class ExportInfo {
* @type {boolean | undefined}
*/
this.canMangleUse = initFrom ? initFrom.canMangleUse : undefined;
/** @type {boolean} */
this.exportsInfoOwned = false;
/** @type {ExportsInfo=} */
this.exportsInfo = undefined;
}
Expand Down Expand Up @@ -511,6 +516,18 @@ class ExportInfo {
return this.usedName || this.name || fallbackName;
}

createNestedExportsInfo() {
if (this.exportsInfoOwned) return this.exportsInfo;
this.exportsInfoOwned = true;
this.exportsInfo = new ExportsInfo();
this.exportsInfo.setHasProvideInfo();
return this.exportsInfo;
}

getNestedExportsInfo() {
return this.exportsInfo;
}

getUsedInfo() {
switch (this.used) {
case UsageState.NoInfo:
Expand Down
6 changes: 3 additions & 3 deletions lib/RuntimeTemplate.js
Original file line number Diff line number Diff line change
Expand Up @@ -593,7 +593,7 @@ class RuntimeTemplate {

if (exportsType === "named") {
if (exportName.length > 0 && exportName[0] === "default") {
return `${importVar}${propertyAccess(exportName, 1)}`;
exportName = exportName.slice(1);
} else if (exportName.length === 0) {
return `${importVar}_namespace`;
}
Expand All @@ -604,13 +604,13 @@ class RuntimeTemplate {
const used = exportsInfo.getUsedName(exportName);
if (!used) {
const comment = Template.toNormalComment(
`unused export ${exportName.join(".")}`
`unused export ${propertyAccess(exportName)}`
);
return `${comment} undefined`;
}
const comment = arrayEquals(used, exportName)
? ""
: Template.toNormalComment(exportName.join(".")) + " ";
: Template.toNormalComment(propertyAccess(exportName)) + " ";
const access = `${importVar}${comment}${propertyAccess(used)}`;
if (isCall && callContext === false) {
if (asiSafe) {
Expand Down
97 changes: 97 additions & 0 deletions lib/dependencies/JsonExportsDependency.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/

"use strict";

const makeSerializable = require("../util/makeSerializable");
const NullDependency = require("./NullDependency");

/** @typedef {import("../ChunkGraph")} ChunkGraph */
/** @typedef {import("../Dependency").ExportSpec} ExportSpec */
/** @typedef {import("../Dependency").ExportsSpec} ExportsSpec */
/** @typedef {import("../ModuleGraph")} ModuleGraph */
/** @typedef {import("../util/Hash")} Hash */

const getExportsFromData = data => {
if (data && typeof data === "object") {
if (Array.isArray(data)) {
return data.map((item, idx) => {
return {
name: `${idx}`,
canMangle: true,
exports: getExportsFromData(item)
};
});
} else {
const exports = [];
for (const key of Object.keys(data)) {
exports.push({
name: key,
canMangle: true,
exports: getExportsFromData(data[key])
});
}
return exports;
}
}
return undefined;
};

class JsonExportsDependency extends NullDependency {
/**
* @param {(string | ExportSpec)[]} exports json exports
*/
constructor(exports) {
super();
this.exports = exports;
}

get type() {
return "json exports";
}

/**
* Returns the exported names
* @param {ModuleGraph} moduleGraph module graph
* @returns {ExportsSpec | undefined} export names
*/
getExports(moduleGraph) {
return {
exports: this.exports,
dependencies: undefined
};
}

/**
* Update the hash
* @param {Hash} hash hash to be updated
* @param {ChunkGraph} chunkGraph chunk graph
* @returns {void}
*/
updateHash(hash, chunkGraph) {
hash.update(this.exports ? JSON.stringify(this.exports) : "undefined");
super.updateHash(hash, chunkGraph);
}

serialize(context) {
const { write } = context;
write(this.exports);
super.serialize(context);
}

deserialize(context) {
const { read } = context;
this.exports = read();
super.deserialize(context);
}
}

makeSerializable(
JsonExportsDependency,
"webpack/lib/dependencies/JsonExportsDependency"
);

module.exports = JsonExportsDependency;
module.exports.getExportsFromData = getExportsFromData;

0 comments on commit 14ee25c

Please sign in to comment.