Merge pull request #17203 from bworline/ns

Normalize property accessors for es6 namespaces and chained member/call expressions
This commit is contained in:
Alexander Akait 2023-05-31 21:39:09 +03:00 committed by GitHub
commit 53c98f06ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 344 additions and 34 deletions

View File

@ -35,7 +35,7 @@ class HarmonyEvaluatedImportSpecifierDependency extends HarmonyImportSpecifierDe
* @param {string} operator operator * @param {string} operator operator
*/ */
constructor(request, sourceOrder, ids, name, range, assertions, operator) { constructor(request, sourceOrder, ids, name, range, assertions, operator) {
super(request, sourceOrder, ids, name, range, false, assertions); super(request, sourceOrder, ids, name, range, false, assertions, []);
this.operator = operator; this.operator = operator;
} }

View File

@ -195,7 +195,8 @@ module.exports = class HarmonyImportDependencyParserPlugin {
settings.name, settings.name,
expr.range, expr.range,
exportPresenceMode, exportPresenceMode,
settings.assertions settings.assertions,
[]
); );
dep.referencedPropertiesInDestructuring = dep.referencedPropertiesInDestructuring =
parser.destructuringAssignmentPropertiesFor(expr); parser.destructuringAssignmentPropertiesFor(expr);
@ -211,7 +212,7 @@ module.exports = class HarmonyImportDependencyParserPlugin {
.for(harmonySpecifierTag) .for(harmonySpecifierTag)
.tap( .tap(
"HarmonyImportDependencyParserPlugin", "HarmonyImportDependencyParserPlugin",
(expression, members, membersOptionals) => { (expression, members, membersOptionals, memberRangeStarts) => {
const settings = /** @type {HarmonySettings} */ ( const settings = /** @type {HarmonySettings} */ (
parser.currentTagData parser.currentTagData
); );
@ -219,6 +220,11 @@ module.exports = class HarmonyImportDependencyParserPlugin {
members, members,
membersOptionals membersOptionals
); );
const rangeStarts = memberRangeStarts.slice(
0,
memberRangeStarts.length -
(members.length - nonOptionalMembers.length)
);
const expr = const expr =
nonOptionalMembers !== members nonOptionalMembers !== members
? getNonOptionalMemberChain( ? getNonOptionalMemberChain(
@ -234,7 +240,8 @@ module.exports = class HarmonyImportDependencyParserPlugin {
settings.name, settings.name,
expr.range, expr.range,
exportPresenceMode, exportPresenceMode,
settings.assertions settings.assertions,
rangeStarts
); );
dep.referencedPropertiesInDestructuring = dep.referencedPropertiesInDestructuring =
parser.destructuringAssignmentPropertiesFor(expr); parser.destructuringAssignmentPropertiesFor(expr);
@ -249,7 +256,7 @@ module.exports = class HarmonyImportDependencyParserPlugin {
.for(harmonySpecifierTag) .for(harmonySpecifierTag)
.tap( .tap(
"HarmonyImportDependencyParserPlugin", "HarmonyImportDependencyParserPlugin",
(expression, members, membersOptionals) => { (expression, members, membersOptionals, memberRangeStarts) => {
const { arguments: args, callee } = expression; const { arguments: args, callee } = expression;
const settings = /** @type {HarmonySettings} */ ( const settings = /** @type {HarmonySettings} */ (
parser.currentTagData parser.currentTagData
@ -258,6 +265,11 @@ module.exports = class HarmonyImportDependencyParserPlugin {
members, members,
membersOptionals membersOptionals
); );
const rangeStarts = memberRangeStarts.slice(
0,
memberRangeStarts.length -
(members.length - nonOptionalMembers.length)
);
const expr = const expr =
nonOptionalMembers !== members nonOptionalMembers !== members
? getNonOptionalMemberChain( ? getNonOptionalMemberChain(
@ -273,7 +285,8 @@ module.exports = class HarmonyImportDependencyParserPlugin {
settings.name, settings.name,
expr.range, expr.range,
exportPresenceMode, exportPresenceMode,
settings.assertions settings.assertions,
rangeStarts
); );
dep.directImport = members.length === 0; dep.directImport = members.length === 0;
dep.call = true; dep.call = true;

View File

@ -43,6 +43,7 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency {
* @param {Range} range range * @param {Range} range range
* @param {TODO} exportPresenceMode export presence mode * @param {TODO} exportPresenceMode export presence mode
* @param {Assertions=} assertions assertions * @param {Assertions=} assertions assertions
* @param {number[]=} idRangeStarts range starts for members of ids; the two arrays are right-aligned
*/ */
constructor( constructor(
request, request,
@ -51,12 +52,14 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency {
name, name,
range, range,
exportPresenceMode, exportPresenceMode,
assertions assertions,
idRangeStarts // TODO webpack 6 make this non-optional. It must always be set to properly trim ids.
) { ) {
super(request, sourceOrder, assertions); super(request, sourceOrder, assertions);
this.ids = ids; this.ids = ids;
this.name = name; this.name = name;
this.range = range; this.range = range;
this.idRangeStarts = idRangeStarts;
this.exportPresenceMode = exportPresenceMode; this.exportPresenceMode = exportPresenceMode;
this.namespaceObjectAsContext = false; this.namespaceObjectAsContext = false;
this.call = undefined; this.call = undefined;
@ -258,6 +261,7 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency {
write(this.ids); write(this.ids);
write(this.name); write(this.name);
write(this.range); write(this.range);
write(this.idRangeStarts);
write(this.exportPresenceMode); write(this.exportPresenceMode);
write(this.namespaceObjectAsContext); write(this.namespaceObjectAsContext);
write(this.call); write(this.call);
@ -277,6 +281,7 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency {
this.ids = read(); this.ids = read();
this.name = read(); this.name = read();
this.range = read(); this.range = read();
this.idRangeStarts = read();
this.exportPresenceMode = read(); this.exportPresenceMode = read();
this.namespaceObjectAsContext = read(); this.namespaceObjectAsContext = read();
this.call = read(); this.call = read();
@ -310,14 +315,78 @@ HarmonyImportSpecifierDependency.Template = class HarmonyImportSpecifierDependen
// Skip rendering depending when dependency is conditional // Skip rendering depending when dependency is conditional
if (connection && !connection.isTargetActive(runtime)) return; if (connection && !connection.isTargetActive(runtime)) return;
const ids = dep.getIds(moduleGraph); const ids = dep.getIds(moduleGraph); // determine minimal set of IDs.
const exportExpr = this._getCodeForIds(dep, source, templateContext, ids); let trimmedIds = this._trimIdsToThoseImported(ids, moduleGraph, dep);
const range = dep.range;
if (dep.shorthand) { let [rangeStart, rangeEnd] = dep.range;
source.insert(range[1], `: ${exportExpr}`); if (trimmedIds.length !== ids.length) {
} else { // The array returned from dep.idRangeStarts is right-aligned with the array returned from dep.getIds.
source.replace(range[0], range[1] - 1, exportExpr); // Meaning, the two arrays may not always have the same number of elements, but the last element of
// dep.idRangeStarts corresponds to [the starting range position of] the last element of dep.getIds.
// Use this to find the correct range end position based on the number of ids that were trimmed.
const idx =
dep.idRangeStarts === undefined
? -1 /* trigger failure case below */
: dep.idRangeStarts.length + (trimmedIds.length - ids.length);
if (idx < 0 || idx >= dep.idRangeStarts.length) {
// cspell:ignore minifiers
// Should not happen but we can't throw an error here because of backward compatibility with
// external plugins in wp5. Instead, we just disable trimming for now. This may break some minifiers.
trimmedIds = ids;
// TODO webpack 6 remove the "trimmedIds = ids" above and uncomment the following line instead.
// throw new Error("Missing range starts data for id replacement trimming.");
} else {
rangeEnd = dep.idRangeStarts[idx];
}
} }
const exportExpr = this._getCodeForIds(
dep,
source,
templateContext,
trimmedIds
);
if (dep.shorthand) {
source.insert(rangeEnd, `: ${exportExpr}`);
} else {
source.replace(rangeStart, rangeEnd - 1, exportExpr);
}
}
/**
* @summary Determine which IDs in the id chain are actually referring to namespaces or imports,
* and which are deeper member accessors on the imported object. Only the former should be re-rendered.
* @param {string[]} ids ids
* @param {ModuleGraph} moduleGraph moduleGraph
* @param {HarmonyImportSpecifierDependency} dependency dependency
* @returns {string[]} generated code
*/
_trimIdsToThoseImported(ids, moduleGraph, dependency) {
let trimmedIds = [];
const exportsInfo = moduleGraph.getExportsInfo(
moduleGraph.getModule(dependency)
);
let currentExportsInfo = /** @type {ExportsInfo=} */ exportsInfo;
for (let i = 0; i < ids.length; i++) {
if (i === 0 && ids[i] === "default") {
continue; // ExportInfo for the next level under default is still at the root ExportsInfo, so don't advance currentExportsInfo
}
const exportInfo = currentExportsInfo.getExportInfo(ids[i]);
if (exportInfo.provided === false) {
// json imports have nested ExportInfo for elements that things that are not actually exported, so check .provided
trimmedIds = ids.slice(0, i);
break;
}
const nestedInfo = exportInfo.getNestedExportsInfo();
if (!nestedInfo) {
// once all nested exports are traversed, the next item is the actual import so stop there
trimmedIds = ids.slice(0, i + 1);
break;
}
currentExportsInfo = nestedInfo;
}
// Never trim to nothing. This can happen for invalid imports (e.g. import { notThere } from "./module", or import { anything } from "./missingModule")
return trimmedIds.length ? trimmedIds : ids;
} }
/** /**

View File

@ -70,6 +70,8 @@ class BasicEvaluatedExpression {
this.getMembers = undefined; this.getMembers = undefined;
/** @type {() => boolean[]} */ /** @type {() => boolean[]} */
this.getMembersOptionals = undefined; this.getMembersOptionals = undefined;
/** @type {() => number[]} */
this.getMemberRangeStarts = undefined;
/** @type {EsTreeNode} */ /** @type {EsTreeNode} */
this.expression = undefined; this.expression = undefined;
} }
@ -384,14 +386,22 @@ class BasicEvaluatedExpression {
* @param {string | VariableInfoInterface} rootInfo root info * @param {string | VariableInfoInterface} rootInfo root info
* @param {() => string[]} getMembers members * @param {() => string[]} getMembers members
* @param {() => boolean[]=} getMembersOptionals optional members * @param {() => boolean[]=} getMembersOptionals optional members
* @param {() => number[]=} getMemberRangeStarts range start of progressively increasing sub-expressions
* @returns {this} this * @returns {this} this
*/ */
setIdentifier(identifier, rootInfo, getMembers, getMembersOptionals) { setIdentifier(
identifier,
rootInfo,
getMembers,
getMembersOptionals,
getMemberRangeStarts
) {
this.type = TypeIdentifier; this.type = TypeIdentifier;
this.identifier = identifier; this.identifier = identifier;
this.rootInfo = rootInfo; this.rootInfo = rootInfo;
this.getMembers = getMembers; this.getMembers = getMembers;
this.getMembersOptionals = getMembersOptionals; this.getMembersOptionals = getMembersOptionals;
this.getMemberRangeStarts = getMemberRangeStarts;
this.sideEffects = true; this.sideEffects = true;
return this; return this;
} }

View File

@ -91,7 +91,7 @@ const BasicEvaluatedExpression = require("./BasicEvaluatedExpression");
/** @typedef {import("../Parser").ParserState} ParserState */ /** @typedef {import("../Parser").ParserState} ParserState */
/** @typedef {import("../Parser").PreparsedAst} PreparsedAst */ /** @typedef {import("../Parser").PreparsedAst} PreparsedAst */
/** @typedef {{declaredScope: ScopeInfo, freeName: string | true, tagInfo: TagInfo | undefined}} VariableInfoInterface */ /** @typedef {{declaredScope: ScopeInfo, freeName: string | true, tagInfo: TagInfo | undefined}} VariableInfoInterface */
/** @typedef {{ name: string | VariableInfo, rootInfo: string | VariableInfo, getMembers: () => string[], getMembersOptionals: () => boolean[] }} GetInfoResult */ /** @typedef {{ name: string | VariableInfo, rootInfo: string | VariableInfo, getMembers: () => string[], getMembersOptionals: () => boolean[], getMemberRangeStarts: () => number[] }} GetInfoResult */
const EMPTY_ARRAY = []; const EMPTY_ARRAY = [];
const ALLOWED_MEMBER_TYPES_CALL_EXPRESSION = 0b01; const ALLOWED_MEMBER_TYPES_CALL_EXPRESSION = 0b01;
@ -350,9 +350,15 @@ class JavascriptParser extends Parser {
/** @type {HookMap<SyncBailHook<[BaseCallExpression], boolean | void>>} */ /** @type {HookMap<SyncBailHook<[BaseCallExpression], boolean | void>>} */
call: new HookMap(() => new SyncBailHook(["expression"])), call: new HookMap(() => new SyncBailHook(["expression"])),
/** Something like "a.b()" */ /** Something like "a.b()" */
/** @type {HookMap<SyncBailHook<[CallExpression, string[], boolean[]], boolean | void>>} */ /** @type {HookMap<SyncBailHook<[CallExpression, string[], boolean[], number[]], boolean | void>>} */
callMemberChain: new HookMap( callMemberChain: new HookMap(
() => new SyncBailHook(["expression", "members", "membersOptionals"]) () =>
new SyncBailHook([
"expression",
"members",
"membersOptionals",
"memberRangeStarts"
])
), ),
/** Something like "a.b().c.d" */ /** Something like "a.b().c.d" */
/** @type {HookMap<SyncBailHook<[Expression, string[], CallExpression, string[]], boolean | void>>} */ /** @type {HookMap<SyncBailHook<[Expression, string[], CallExpression, string[]], boolean | void>>} */
@ -384,9 +390,15 @@ class JavascriptParser extends Parser {
binaryExpression: new SyncBailHook(["binaryExpression"]), binaryExpression: new SyncBailHook(["binaryExpression"]),
/** @type {HookMap<SyncBailHook<[Expression], boolean | void>>} */ /** @type {HookMap<SyncBailHook<[Expression], boolean | void>>} */
expression: new HookMap(() => new SyncBailHook(["expression"])), expression: new HookMap(() => new SyncBailHook(["expression"])),
/** @type {HookMap<SyncBailHook<[Expression, string[], boolean[]], boolean | void>>} */ /** @type {HookMap<SyncBailHook<[Expression, string[], boolean[], number[]], boolean | void>>} */
expressionMemberChain: new HookMap( expressionMemberChain: new HookMap(
() => new SyncBailHook(["expression", "members", "membersOptionals"]) () =>
new SyncBailHook([
"expression",
"members",
"membersOptionals",
"memberRangeStarts"
])
), ),
/** @type {HookMap<SyncBailHook<[Expression, string[]], boolean | void>>} */ /** @type {HookMap<SyncBailHook<[Expression, string[]], boolean | void>>} */
unhandledExpressionMemberChain: new HookMap( unhandledExpressionMemberChain: new HookMap(
@ -1150,7 +1162,8 @@ class JavascriptParser extends Parser {
info.name, info.name,
info.rootInfo, info.rootInfo,
info.getMembers, info.getMembers,
info.getMembersOptionals info.getMembersOptionals,
info.getMemberRangeStarts
) )
.setRange(expr.range); .setRange(expr.range);
} }
@ -1170,7 +1183,8 @@ class JavascriptParser extends Parser {
name: info, name: info,
rootInfo: info, rootInfo: info,
getMembers: () => [], getMembers: () => [],
getMembersOptionals: () => [] getMembersOptionals: () => [],
getMemberRangeStarts: () => []
}; };
} }
}); });
@ -1184,7 +1198,8 @@ class JavascriptParser extends Parser {
name: info, name: info,
rootInfo: info, rootInfo: info,
getMembers: () => [], getMembers: () => [],
getMembersOptionals: () => [] getMembersOptionals: () => [],
getMemberRangeStarts: () => []
}; };
} }
}); });
@ -3248,7 +3263,8 @@ class JavascriptParser extends Parser {
callee.getMembers(), callee.getMembers(),
callee.getMembersOptionals callee.getMembersOptionals
? callee.getMembersOptionals() ? callee.getMembersOptionals()
: callee.getMembers().map(() => false) : callee.getMembers().map(() => false),
callee.getMemberRangeStarts ? callee.getMemberRangeStarts() : []
); );
if (result1 === true) return; if (result1 === true) return;
const result2 = this.callHooksForInfo( const result2 = this.callHooksForInfo(
@ -3292,12 +3308,14 @@ class JavascriptParser extends Parser {
if (result1 === true) return; if (result1 === true) return;
const members = exprInfo.getMembers(); const members = exprInfo.getMembers();
const membersOptionals = exprInfo.getMembersOptionals(); const membersOptionals = exprInfo.getMembersOptionals();
const memberRangeStarts = exprInfo.getMemberRangeStarts();
const result2 = this.callHooksForInfo( const result2 = this.callHooksForInfo(
this.hooks.expressionMemberChain, this.hooks.expressionMemberChain,
exprInfo.rootInfo, exprInfo.rootInfo,
expression, expression,
members, members,
membersOptionals membersOptionals,
memberRangeStarts
); );
if (result2 === true) return; if (result2 === true) return;
this.walkMemberExpressionWithExpressionName( this.walkMemberExpressionWithExpressionName(
@ -4253,20 +4271,23 @@ class JavascriptParser extends Parser {
/** /**
* @param {MemberExpression} expression a member expression * @param {MemberExpression} expression a member expression
* @returns {{ members: string[], object: Expression | Super, membersOptionals: boolean[] }} member names (reverse order) and remaining object * @returns {{ members: string[], object: Expression | Super, membersOptionals: boolean[], memberRangeStarts: number[] }} member names (reverse order) and remaining object
*/ */
extractMemberExpressionChain(expression) { extractMemberExpressionChain(expression) {
/** @type {AnyNode} */ /** @type {AnyNode} */
let expr = expression; let expr = expression;
const members = []; const members = [];
const membersOptionals = []; const membersOptionals = [];
const memberRangeStarts = [];
while (expr.type === "MemberExpression") { while (expr.type === "MemberExpression") {
if (expr.computed) { if (expr.computed) {
if (expr.property.type !== "Literal") break; if (expr.property.type !== "Literal") break;
members.push(`${expr.property.value}`); members.push(`${expr.property.value}`);
memberRangeStarts.push(expr.object.range[1]);
} else { } else {
if (expr.property.type !== "Identifier") break; if (expr.property.type !== "Identifier") break;
members.push(expr.property.name); members.push(expr.property.name);
memberRangeStarts.push(expr.object.range[1]);
} }
membersOptionals.push(expr.optional); membersOptionals.push(expr.optional);
expr = expr.object; expr = expr.object;
@ -4275,6 +4296,7 @@ class JavascriptParser extends Parser {
return { return {
members, members,
membersOptionals, membersOptionals,
memberRangeStarts,
object: expr object: expr
}; };
} }
@ -4297,8 +4319,8 @@ class JavascriptParser extends Parser {
return { info, name }; return { info, name };
} }
/** @typedef {{ type: "call", call: CallExpression, calleeName: string, rootInfo: string | VariableInfo, getCalleeMembers: () => string[], name: string, getMembers: () => string[], getMembersOptionals: () => boolean[]}} CallExpressionInfo */ /** @typedef {{ type: "call", call: CallExpression, calleeName: string, rootInfo: string | VariableInfo, getCalleeMembers: () => string[], name: string, getMembers: () => string[], getMembersOptionals: () => boolean[], getMemberRangeStarts: () => number[]}} CallExpressionInfo */
/** @typedef {{ type: "expression", rootInfo: string | VariableInfo, name: string, getMembers: () => string[], getMembersOptionals: () => boolean[]}} ExpressionExpressionInfo */ /** @typedef {{ type: "expression", rootInfo: string | VariableInfo, name: string, getMembers: () => string[], getMembersOptionals: () => boolean[], getMemberRangeStarts: () => number[]}} ExpressionExpressionInfo */
/** /**
* @param {MemberExpression} expression a member expression * @param {MemberExpression} expression a member expression
@ -4306,7 +4328,7 @@ class JavascriptParser extends Parser {
* @returns {CallExpressionInfo | ExpressionExpressionInfo | undefined} expression info * @returns {CallExpressionInfo | ExpressionExpressionInfo | undefined} expression info
*/ */
getMemberExpressionInfo(expression, allowedTypes) { getMemberExpressionInfo(expression, allowedTypes) {
const { object, members, membersOptionals } = const { object, members, membersOptionals, memberRangeStarts } =
this.extractMemberExpressionChain(expression); this.extractMemberExpressionChain(expression);
switch (object.type) { switch (object.type) {
case "CallExpression": { case "CallExpression": {
@ -4332,7 +4354,8 @@ class JavascriptParser extends Parser {
getCalleeMembers: memoize(() => rootMembers.reverse()), getCalleeMembers: memoize(() => rootMembers.reverse()),
name: objectAndMembersToName(`${calleeName}()`, members), name: objectAndMembersToName(`${calleeName}()`, members),
getMembers: memoize(() => members.reverse()), getMembers: memoize(() => members.reverse()),
getMembersOptionals: memoize(() => membersOptionals.reverse()) getMembersOptionals: memoize(() => membersOptionals.reverse()),
getMemberRangeStarts: memoize(() => memberRangeStarts.reverse())
}; };
} }
case "Identifier": case "Identifier":
@ -4351,7 +4374,8 @@ class JavascriptParser extends Parser {
name: objectAndMembersToName(resolvedRoot, members), name: objectAndMembersToName(resolvedRoot, members),
rootInfo, rootInfo,
getMembers: memoize(() => members.reverse()), getMembers: memoize(() => members.reverse()),
getMembersOptionals: memoize(() => membersOptionals.reverse()) getMembersOptionals: memoize(() => membersOptionals.reverse()),
getMemberRangeStarts: memoize(() => memberRangeStarts.reverse())
}; };
} }
} }

View File

@ -0,0 +1,5 @@
{
"nested": {
"object3": {}
}
}

View File

@ -0,0 +1,63 @@
import { obj1 } from './module1';
import * as m_1 from './module1';
import * as m_2 from './module2';
import * as m_3 from './module3';
import data from "./data";
const { expectSourceToContain } = require("../../../helpers/expectSource");
// It's important to preserve the same accessor syntax (quotes vs. dot notatation) after the actual export variable.
// Else, minifiers such as Closure Compiler will not be able to minify correctly in ADVANCED mode.
it("should use/preserve accessor form for import object and namespaces", function() {
var fs = require("fs");
var source = fs.readFileSync(__filename, "utf-8").toString();
// Reference the imports to generate uses in the source.
const f = false;
if (f) {
const x1 = m_1;
const x2 = obj1;
const z1 = obj1["plants"];
const z2 = obj1["funcs"]();
const z3 = m_1["obj1"]["pots"];
const z4 = m_1["obj1"]["subs"]();
const a = m_2["m_1"].obj1["flip"].flap;
const b = m_2["m_1"]["obj1"].zip["zap"];
const c = m_2.m_1.obj1["ding"].dong();
const d = m_2.m_1["obj1"].sing["song"]();
const aa = m_3["m_2"].m_1["obj1"]["zoom"];
const bb = obj1.up.down?.left.right;
data.nested.object3["unknownProperty"].depth = "deep";
}
/************ DO NOT MATCH BELOW THIS LINE ************/
// Imported objects and import namespaces should use dot notation. Any references to the properties of exports
// should be preserved as either quotes or dot notation, depending on the original source.
expectSourceToContain(source, 'const x1 = module1_namespaceObject;');
expectSourceToContain(source, 'const x2 = obj1;');
expectSourceToContain(source, 'const z1 = obj1["plants"];');
expectSourceToContain(source, 'const z2 = obj1["funcs"]();');
expectSourceToContain(source, 'const z3 = obj1["pots"];');
expectSourceToContain(source, 'const z4 = obj1["subs"]();');
expectSourceToContain(source, 'const a = obj1["flip"].flap;');
expectSourceToContain(source, 'const b = obj1.zip["zap"];');
expectSourceToContain(source, 'const c = obj1["ding"].dong();');
expectSourceToContain(source, 'const d = obj1.sing["song"]();');
expectSourceToContain(source, 'const aa = obj1["zoom"];');
expectSourceToContain(source, 'const bb = obj1.up.down?.left.right;');
expectSourceToContain(source, 'data_namespaceObject.a.a["unknownProperty"].depth = "deep";');
});

View File

@ -0,0 +1,3 @@
export const obj1 = {};
export default { obj2: {} };

View File

@ -0,0 +1,2 @@
import * as m1 from './module1';
export { m1 as m_1 };

View File

@ -0,0 +1,2 @@
import * as m2 from './module2';
export { m2 as m_2 };

View File

@ -0,0 +1,5 @@
var supportsOptionalChaining = require("../../../helpers/supportsOptionalChaining");
module.exports = function (config) {
return supportsOptionalChaining();
};

View File

@ -0,0 +1,11 @@
/** @type {import("../../../../").Configuration} */
module.exports = {
node: {
__dirname: false,
__filename: false
},
mode: "production",
optimization: {
mangleExports: "size"
}
};

View File

@ -0,0 +1,5 @@
{
"nested": {
"object3": {}
}
}

View File

@ -0,0 +1,64 @@
import { obj1 } from './module1';
import * as m_1 from './module1';
import * as m_2 from './module2';
import * as m_3 from './module3';
import data from "./data";
const { expectSourceToContain } = require("../../../helpers/expectSource");
// It's important to preserve the same accessor syntax (quotes vs. dot notatation) after the actual export variable.
// Else, minifiers such as Closure Compiler will not be able to minify correctly in ADVANCED mode.
it("should use/preserve accessor form for import object and namespaces", function() {
var fs = require("fs");
var source = fs.readFileSync(__filename, "utf-8").toString();
// Reference the imports to generate uses in the source.
const f = false;
if (f) {
const x1 = m_1;
const x2 = obj1;
const z1 = obj1["plants"];
const z2 = obj1["funcs"]();
const z3 = m_1["obj1"]["pots"];
const z4 = m_1["obj1"]["subs"]();
const a = m_2["m_1"].obj1["flip"].flap;
const b = m_2["m_1"]["obj1"].zip["zap"];
const c = m_2.m_1.obj1["ding"].dong();
const d = m_2.m_1["obj1"].sing["song"]();
const aa = m_3["m_2"].m_1["obj1"]["zoom"];
const bb = obj1.up.down?.left.right;
data.nested.object3["unknownProperty"].depth = "deep";
}
/************ DO NOT MATCH BELOW THIS LINE ************/
// Imported objects and import namespaces should use dot notation. Any references to the properties of exports
// should be preserved as either quotes or dot notation, depending on the original source.
expectSourceToContain(source, 'const x1 = _module1__WEBPACK_IMPORTED_MODULE_0__;');
expectSourceToContain(source, 'const x2 = _module1__WEBPACK_IMPORTED_MODULE_0__.obj1;');
expectSourceToContain(source, 'const z1 = _module1__WEBPACK_IMPORTED_MODULE_0__.obj1["plants"];');
expectSourceToContain(source, 'const z2 = _module1__WEBPACK_IMPORTED_MODULE_0__.obj1["funcs"]();');
expectSourceToContain(source, 'const z3 = _module1__WEBPACK_IMPORTED_MODULE_0__.obj1["pots"];');
expectSourceToContain(source, 'const z4 = _module1__WEBPACK_IMPORTED_MODULE_0__.obj1["subs"]();');
expectSourceToContain(source, 'const a = _module2__WEBPACK_IMPORTED_MODULE_1__.m_1.obj1["flip"].flap;');
expectSourceToContain(source, 'const b = _module2__WEBPACK_IMPORTED_MODULE_1__.m_1.obj1.zip["zap"];');
expectSourceToContain(source, 'const c = _module2__WEBPACK_IMPORTED_MODULE_1__.m_1.obj1["ding"].dong();');
expectSourceToContain(source, 'const d = _module2__WEBPACK_IMPORTED_MODULE_1__.m_1.obj1.sing["song"]();');
expectSourceToContain(source, 'const aa = _module3__WEBPACK_IMPORTED_MODULE_2__.m_2.m_1.obj1["zoom"];');
expectSourceToContain(source, 'const bb = _module1__WEBPACK_IMPORTED_MODULE_0__.obj1.up.down?.left.right;');
expectSourceToContain(source, '_data__WEBPACK_IMPORTED_MODULE_3__.nested.object3["unknownProperty"].depth = "deep";');
});

View File

@ -0,0 +1,3 @@
export const obj1 = {};
export default { obj2: {} };

View File

@ -0,0 +1,2 @@
import * as m1 from './module1';
export { m1 as m_1 };

View File

@ -0,0 +1,2 @@
import * as m2 from './module2';
export { m2 as m_2 };

View File

@ -0,0 +1,5 @@
var supportsOptionalChaining = require("../../../helpers/supportsOptionalChaining");
module.exports = function (config) {
return supportsOptionalChaining();
};

View File

@ -0,0 +1,14 @@
/** @type {import("../../../../").Configuration} */
module.exports = {
node: {
__dirname: false,
__filename: false
},
optimization: {
concatenateModules: false,
usedExports: true,
providedExports: true,
minimize: false,
mangleExports: false
}
};

14
types.d.ts vendored
View File

@ -507,6 +507,7 @@ declare abstract class BasicEvaluatedExpression {
rootInfo: string | VariableInfoInterface; rootInfo: string | VariableInfoInterface;
getMembers: () => string[]; getMembers: () => string[];
getMembersOptionals: () => boolean[]; getMembersOptionals: () => boolean[];
getMemberRangeStarts: () => number[];
expression: NodeEstreeIndex; expression: NodeEstreeIndex;
isUnknown(): boolean; isUnknown(): boolean;
isNull(): boolean; isNull(): boolean;
@ -591,7 +592,8 @@ declare abstract class BasicEvaluatedExpression {
identifier: string | VariableInfoInterface, identifier: string | VariableInfoInterface,
rootInfo: string | VariableInfoInterface, rootInfo: string | VariableInfoInterface,
getMembers: () => string[], getMembers: () => string[],
getMembersOptionals?: () => boolean[] getMembersOptionals?: () => boolean[],
getMemberRangeStarts?: () => number[]
): BasicEvaluatedExpression; ): BasicEvaluatedExpression;
/** /**
@ -786,6 +788,7 @@ declare interface CallExpressionInfo {
name: string; name: string;
getMembers: () => string[]; getMembers: () => string[];
getMembersOptionals: () => boolean[]; getMembersOptionals: () => boolean[];
getMemberRangeStarts: () => number[];
} }
declare interface CallbackAsyncQueue<T> { declare interface CallbackAsyncQueue<T> {
(err?: null | WebpackError, result?: T): any; (err?: null | WebpackError, result?: T): any;
@ -3988,6 +3991,7 @@ declare interface ExpressionExpressionInfo {
name: string; name: string;
getMembers: () => string[]; getMembers: () => string[];
getMembersOptionals: () => boolean[]; getMembersOptionals: () => boolean[];
getMemberRangeStarts: () => number[];
} }
declare interface ExtensionAliasOption { declare interface ExtensionAliasOption {
alias: string | string[]; alias: string | string[];
@ -5339,7 +5343,10 @@ declare class JavascriptParser extends Parser {
topLevelAwait: SyncBailHook<[Expression], boolean | void>; topLevelAwait: SyncBailHook<[Expression], boolean | void>;
call: HookMap<SyncBailHook<[BaseCallExpression], boolean | void>>; call: HookMap<SyncBailHook<[BaseCallExpression], boolean | void>>;
callMemberChain: HookMap< callMemberChain: HookMap<
SyncBailHook<[CallExpression, string[], boolean[]], boolean | void> SyncBailHook<
[CallExpression, string[], boolean[], number[]],
boolean | void
>
>; >;
memberChainOfCallMemberChain: HookMap< memberChainOfCallMemberChain: HookMap<
SyncBailHook< SyncBailHook<
@ -5358,7 +5365,7 @@ declare class JavascriptParser extends Parser {
binaryExpression: SyncBailHook<[BinaryExpression], boolean | void>; binaryExpression: SyncBailHook<[BinaryExpression], boolean | void>;
expression: HookMap<SyncBailHook<[Expression], boolean | void>>; expression: HookMap<SyncBailHook<[Expression], boolean | void>>;
expressionMemberChain: HookMap< expressionMemberChain: HookMap<
SyncBailHook<[Expression, string[], boolean[]], boolean | void> SyncBailHook<[Expression, string[], boolean[], number[]], boolean | void>
>; >;
unhandledExpressionMemberChain: HookMap< unhandledExpressionMemberChain: HookMap<
SyncBailHook<[Expression, string[]], boolean | void> SyncBailHook<[Expression, string[]], boolean | void>
@ -5947,6 +5954,7 @@ declare class JavascriptParser extends Parser {
| YieldExpression | YieldExpression
| Super; | Super;
membersOptionals: boolean[]; membersOptionals: boolean[];
memberRangeStarts: number[];
}; };
getFreeInfoFromVariable(varName: string): { getFreeInfoFromVariable(varName: string): {
name: string; name: string;