Skip to content

Instantly share code, notes, and snippets.

@Delagen
Created February 1, 2018 15:45
Show Gist options
  • Save Delagen/bcfe369b3f0bff20866840533d12ac8c to your computer and use it in GitHub Desktop.
Save Delagen/bcfe369b3f0bff20866840533d12ac8c to your computer and use it in GitHub Desktop.
"use strict";
var _ = require("lodash");
var url = require("url");
var path = require("path");
var typescript = require("typescript");
var loaderUtils = require("loader-utils");
function /*jshint maxparams:5*/ getReplacements(fileName, fileContent, resolveModuleNameCallback, resolveModulePathCallback, resolveModuleChunkCallback) {
var tsTree = typescript.createSourceFile(fileName, fileContent, typescript.ScriptTarget.ES5, /*setParentNodes */ true);
var replacements = [];
resolveModuleChunkCallback = resolveModuleChunkCallback || function(moduleName, modulePath) {
return _.kebabCase(moduleName || path.basename(modulePath, ".module"));
};
function delintNode(node) {
switch (node.kind) {
case typescript.SyntaxKind.StringLiteral:
/* replace path in import(modulePath) */
if (_.isMatch(node, {
parent: {
kind: typescript.SyntaxKind.CallExpression,
expression: {
kind: typescript.SyntaxKind.ImportKeyword
}
}
})) {
replacements.push(function(sourceText) {
return sourceText.substring(0, node.getStart()) + node.getText().replace(node.text, resolveModulePathCallback(node.text)) + sourceText.substring(node.getEnd());
});
}
/* generic string as in angular-cli */
if (_.isMatch(node, {
parent: {
kind: typescript.SyntaxKind.PropertyAssignment,
name: {
kind: typescript.SyntaxKind.Identifier,
text: "loadChildren"
},
parent: {
kind: typescript.SyntaxKind.ObjectLiteralExpression
}
}
})) {
replacements.push(function(sourceText) {
var [modulePath, moduleName] = node.text.split("#");
return `${sourceText.substring(0, node.getStart())}function(){\
return import(/* webpackChunkName: "${resolveModuleChunkCallback(moduleName)}"*/"${resolveModulePathCallback(modulePath)}")\
.then(function(module) { return module.${resolveModuleNameCallback(moduleName)}; });\
}${sourceText.substring(node.getEnd())}`;
});
}
break;
case typescript.SyntaxKind.PropertyAccessExpression:
/* replace modulename in then clause of import(modulePath) */
if (_.isMatch(node, {
name: {
kind: typescript.SyntaxKind.Identifier
},
expression: {
kind: typescript.SyntaxKind.Identifier
},
parent: {
kind: typescript.SyntaxKind.ReturnStatement,
parent: {
kind: typescript.SyntaxKind.Block,
parent: {
kind: typescript.SyntaxKind.FunctionExpression,
parameters: [
{kind: typescript.SyntaxKind.Parameter}
]
}
}
}
}) &&
node.expression.text === node.parent.parent.parent.parameters[0].name.text &&
_.endsWith(node.name.text, "Module")) {
replacements.push(function(sourceText) {
return `${sourceText.substring(0, node.name.getStart())}${resolveModuleNameCallback(node.name.text)}${sourceText.substring(node.name.getEnd())}`;
});
}
break;
}
typescript.forEachChild(node, delintNode);
}
delintNode(tsTree);
return replacements;
}
module.exports = function(source, sourcemap) {
this.cacheable && this.cacheable();
var query = loaderUtils.getOptions(this);
var generatedNgFactory = /\.ngfactory\.([tj]s)/.test(this.resource);
var useNgFactory = query && query.ngFactory || generatedNgFactory;
var newSource = source;
var resolveModuleName = _.bind(function(moduleName) {
return moduleName && [moduleName, useNgFactory && "NgFactory" || ""].join("") || "default";
}, this);
var resolveRelativeToContext = _.bind(function(resourcePath) {
var relativeResourcePath = path.relative(this.context, resourcePath);
if (useNgFactory) {
if (generatedNgFactory) {
return "./" + url.resolve(".", relativeResourcePath);
}
var generatedModulePath = path.resolve(path.dirname(resourcePath), path.basename(resourcePath, ".js"));
return "./" + url.resolve(".", path.relative(this.context, generatedModulePath));
}
return "./" + url.resolve(".", relativeResourcePath);
}, this);
var resolveModulePath = _.bind(function(modulePath) {
return `${resolveRelativeToContext(path.resolve(this.context, modulePath))}${useNgFactory && ".ngfactory" || ""}`;
}, this);
var replacements = getReplacements(this.resource, newSource, resolveModuleName, resolveModulePath);
if (replacements.length) {
newSource = _.reduce(replacements.reverse(), function(result, replacement) {
return replacement(result);
}, newSource);
}
if (this.callback) {
this.callback(null, newSource, sourcemap);
}
else {
return newSource;
}
};
@Delagen
Copy link
Author

Delagen commented Feb 1, 2018

JIT: simply typescript postloader
AOT:

{
 test:   /\.module\.js$/,
 loader: "...",
 query:  {
   context:   "<path to ngc generated root>",
   ngFactory: true
}
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment