Skip to content

Commit

Permalink
feat: add globalIdentifierName option (#290)
Browse files Browse the repository at this point in the history
  • Loading branch information
fossamagna authored Apr 24, 2023
1 parent 9ed10f6 commit a2caf13
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 74 deletions.
204 changes: 132 additions & 72 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
'use strict';
"use strict";

const esprima = require('esprima-next');
const estraverse = require('estraverse');
const escodegen = require('escodegen');
const esprima = require("esprima-next");
const estraverse = require("estraverse");
const escodegen = require("escodegen");

function createBaseAST() {
const ast = {};
ast.type = 'Program';
ast.type = "Program";
ast.body = [];
return ast;
}

function createStubFunctionASTNode(functionName, leadingComments, params) {
const node = {
type: 'FunctionDeclaration',
type: "FunctionDeclaration",
id: {
type: 'Identifier',
name: functionName
type: "Identifier",
name: functionName,
},
params: [],
defaults: [],
body: {
type: 'BlockStatement',
body: []
type: "BlockStatement",
body: [],
},
generator: false,
expression: false
expression: false,
};
if (leadingComments) {
node.leadingComments = leadingComments;
Expand All @@ -47,7 +47,10 @@ class EntryPointFunctions {
if (index === -1) {
index = this.functionNames.push(functionName) - 1;
}
this.stubs.set(index, createStubFunctionASTNode(functionName, comments, params));
this.stubs.set(
index,
createStubFunctionASTNode(functionName, comments, params)
);
}

getEntryPointFunctions() {
Expand All @@ -60,18 +63,21 @@ class EntryPointFunctions {
}

class GlobalAssignments {
constructor(exportsIdentifierName = 'exports') {
constructor({ exportsIdentifierName = "exports", globalIdentifierName = "global" }) {
this.stubs = [];
this.functionNames = new Set();
this.exportsIdentifierName = exportsIdentifierName;
this.globalIdentifierName = globalIdentifierName;
}

add(functionName) {
if (this.functionNames.has(functionName)) {
return;
}
this.functionNames.add(functionName);
this.stubs.push(createGlobalAssignmentASTNode(functionName, this.exportsIdentifierName));
this.stubs.push(
createGlobalAssignmentASTNode(functionName, this.exportsIdentifierName, this.globalIdentifierName)
);
}

getGlobalAssignments() {
Expand All @@ -84,61 +90,95 @@ function _generateStubs(ast, options) {
const entryPointFunctions = new EntryPointFunctions();
estraverse.traverse(ast, {
leave: function (node) {
if (node.type === 'ExpressionStatement'
&& isGlobalAssignmentExpression(node.expression)) {
if (
node.type === "ExpressionStatement" &&
isGlobalAssignmentExpression(node.expression)
) {
const functionName = node.expression.left.property.name;
entryPointFunctions.add(functionName, node.expression.right.params, node.leadingComments);
} else if (node.type === 'ExpressionStatement'
&& node.expression.type === 'SequenceExpression') {
entryPointFunctions.add(
functionName,
node.expression.right.params,
node.leadingComments
);
} else if (
node.type === "ExpressionStatement" &&
node.expression.type === "SequenceExpression"
) {
node.expression.expressions.forEach(function (expression) {
if (isGlobalAssignmentExpression(expression)) {
const functionName = expression.left.property.name;
entryPointFunctions.add(functionName, expression.right.params, expression.leadingComments ?
expression.leadingComments : node.leadingComments);
entryPointFunctions.add(
functionName,
expression.right.params,
expression.leadingComments
? expression.leadingComments
: node.leadingComments
);
}
});
}
if (autoGlobalExports) {
if (node.type === 'ExpressionStatement'
&& isNamedExportsAssignmentExpression(node.expression)) {
const functionName = node.expression.left.property.name;
entryPointFunctions.add(functionName, node.expression.right.params, node.leadingComments);
} else if (node.type === 'ExpressionStatement'
&& node.expression.type === 'SequenceExpression') {
if (
node.type === "ExpressionStatement" &&
isNamedExportsAssignmentExpression(node.expression)
) {
const functionName = node.expression.left.property.name;
entryPointFunctions.add(
functionName,
node.expression.right.params,
node.leadingComments
);
} else if (
node.type === "ExpressionStatement" &&
node.expression.type === "SequenceExpression"
) {
node.expression.expressions.forEach(function (expression) {
if (isNamedExportsAssignmentExpression(expression)) {
const functionName = expression.left.property.name;
entryPointFunctions.add(functionName, expression.right.params, expression.leadingComments ?
expression.leadingComments : node.leadingComments);
entryPointFunctions.add(
functionName,
expression.right.params,
expression.leadingComments
? expression.leadingComments
: node.leadingComments
);
}
});
} else if (node.type === 'ExportNamedDeclaration') {
const functionNames = node.specifiers.map(specifier => specifier.local.name);
functionNames.forEach((functionName) => entryPointFunctions.add(functionName));
} else if (node.type === "ExportNamedDeclaration") {
const functionNames = node.specifiers.map(
(specifier) => specifier.local.name
);
functionNames.forEach((functionName) =>
entryPointFunctions.add(functionName)
);
}
}
}
},
});

return entryPointFunctions.getEntryPointFunctions();
}

function isGlobalAssignmentExpression(node) {
return node.type === 'AssignmentExpression'
&& node.operator === '='
&& node.left.type === 'MemberExpression'
&& node.left.object.type === 'Identifier'
&& node.left.object.name === 'global';
return (
node.type === "AssignmentExpression" &&
node.operator === "=" &&
node.left.type === "MemberExpression" &&
node.left.object.type === "Identifier" &&
node.left.object.name === "global"
);
}

function isNamedExportsAssignmentExpression(node) {
return node.type === 'AssignmentExpression'
&& node.operator === '='
&& node.left.type === 'MemberExpression'
&& node.left.object.type === 'Identifier'
&& node.left.object.name === 'exports'
&& node.left.property.type === 'Identifier'
&& node.left.property.name !== 'default';
return (
node.type === "AssignmentExpression" &&
node.operator === "=" &&
node.left.type === "MemberExpression" &&
node.left.object.type === "Identifier" &&
node.left.object.name === "exports" &&
node.left.property.type === "Identifier" &&
node.left.property.name !== "default"
);
}

function generateStubs(ast, options) {
Expand All @@ -148,34 +188,42 @@ function generateStubs(ast, options) {
return escodegen.generate(baseAST, { comment: !!options.comment });
}

function generateGlobalAssignments(ast, { exportsIdentifierName = 'exports'}) {
const globalAssignments = new GlobalAssignments(exportsIdentifierName);
function generateGlobalAssignments(ast, options) {
const globalAssignments = new GlobalAssignments(options);
estraverse.traverse(ast, {
leave: (node) => {
if (node.type === 'ExpressionStatement'
&& isNamedExportsAssignmentExpression(node.expression)) {
const functionName = node.expression.left.property.name;
globalAssignments.add(functionName);
} else if (node.type === 'ExpressionStatement'
&& node.expression.type === 'SequenceExpression') {
if (
node.type === "ExpressionStatement" &&
isNamedExportsAssignmentExpression(node.expression)
) {
const functionName = node.expression.left.property.name;
globalAssignments.add(functionName);
} else if (
node.type === "ExpressionStatement" &&
node.expression.type === "SequenceExpression"
) {
node.expression.expressions.forEach(function (expression) {
if (isNamedExportsAssignmentExpression(expression)) {
const functionName = expression.left.property.name;
globalAssignments.add(functionName);
}
});
} else if (node.type === 'ExportNamedDeclaration') {
const functionNames = node.specifiers.map(specifier => specifier.local.name);
functionNames.forEach((functionName) => globalAssignments.add(functionName));
} else if (node.type === "ExportNamedDeclaration") {
const functionNames = node.specifiers.map(
(specifier) => specifier.local.name
);
functionNames.forEach((functionName) =>
globalAssignments.add(functionName)
);
}
}
},
});
const baseAST = createBaseAST();
baseAST.body.push(...globalAssignments.getGlobalAssignments());
return escodegen.generate(baseAST);
}

function createGlobalAssignmentASTNode(functionName, exportsIdentifierName) {
function createGlobalAssignmentASTNode(functionName, exportsIdentifierName, globalIdentifierName) {
const node = {
type: "ExpressionStatement",
expression: {
Expand All @@ -186,37 +234,49 @@ function createGlobalAssignmentASTNode(functionName, exportsIdentifierName) {
computed: false,
object: {
type: "Identifier",
name: "global"
name: globalIdentifierName,
},
property: {
type: "Identifier",
name: functionName
}
name: functionName,
},
},
right: {
type: "MemberExpression",
computed: false,
object: {
type: "Identifier",
name: exportsIdentifierName // TODO: この名前を外部から指定できるようにしないとダメ。webpackでesnextなら `__webpack_exports__`とする必要がある。
name: exportsIdentifierName,
},
property: {
type: "Identifier",
name: functionName
}
}
}
name: functionName,
},
},
},
};
return node;
}

exports.generate = function(source, options = { comment: false, autoGlobalExports: false, exportsIdentifierName: "exports" }){
const defaultOptions = {
comment: false,
autoGlobalExports: false,
exportsIdentifierName: "exports",
globalIdentifierName: "global",
};

exports.generate = function (
source,
options = defaultOptions
) {
options = Object.assign({}, defaultOptions, options);
const ast = esprima.parseModule(source, { attachComment: options.comment });
const functions = generateStubs(ast, options);
const globalAssignments = options.autoGlobalExports ? generateGlobalAssignments(ast, options) : undefined;
const globalAssignments = options.autoGlobalExports
? generateGlobalAssignments(ast, options)
: undefined;
return {
entryPointFunctions : functions,
globalAssignments
entryPointFunctions: functions,
globalAssignments,
};
}

};
Original file line number Diff line number Diff line change
@@ -1 +1 @@
global.bar = __webpack_exports__.bar;
__webpack_require__.g.bar = __webpack_exports__.bar;
2 changes: 1 addition & 1 deletion test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ test('generate function generate entry functions from export with autoGlobalExpo
const source = fs.readFileSync(__dirname + '/fixtures/esm-source.js', {encoding: 'utf8'});
const expected = fs.readFileSync(__dirname + '/fixtures/esm-expected-autoGlobalExports.js', {encoding: 'utf8'});
const expectedGlobalAssignments = fs.readFileSync(__dirname + '/fixtures/esm-exports-generated-global-assignments-expected.js', {encoding: 'utf8'});
const output = generate(source, { comment: true, autoGlobalExports: true, exportsIdentifierName: '__webpack_exports__'});
const output = generate(source, { comment: true, autoGlobalExports: true, exportsIdentifierName: '__webpack_exports__', globalIdentifierName: '__webpack_require__.g'});
t.equal(output.entryPointFunctions, expected, 'actual output will match expected');
t.equal(output.globalAssignments, expectedGlobalAssignments, 'actual output will match expected');
t.end();
Expand Down

0 comments on commit a2caf13

Please sign in to comment.