Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use typescript to parse source files #70

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{
"vsicons.presets.angular": false
"vsicons.presets.angular": false,
"prettier.printWidth": 100
}
185 changes: 152 additions & 33 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,59 +1,178 @@

var loaderUtils = require("loader-utils");
var ts = require("typescript");

// using: regex, capture groups, and capture group variables.
var templateUrlRegex = /templateUrl\s*:(\s*['"`](.*?)['"`]\s*([,}]))/gm;
var stylesRegex = /styleUrls *:(\s*\[[^\]]*?\])/g;
var stringRegex = /(['`"])((?:[^\\]\\\1|.)*?)\1/g;
function calculateTemplateUrlReplacement(propertyAssignment, replacedPropertyName) {
return [
{
start: propertyAssignment.name.getStart(),
end: propertyAssignment.name.getEnd(),
replacement: replacedPropertyName
},
constructUrlReplacement(propertyAssignment.initializer)
];
}

function replaceStringsWithRequires(string) {
return string.replace(stringRegex, function (match, quote, url) {
if (url.charAt(0) !== ".") {
url = "./" + url;
function calculateStyleUrlsReplacement(propertyAssignment, replacedPropertyName) {
var styleUrlsReplacements = [];
for (var i = 0; i < propertyAssignment.initializer.elements.length; i++) {
styleUrlsReplacements.push(constructUrlReplacement(propertyAssignment.initializer.elements[i]));
}
return [
{
start: propertyAssignment.name.getStart(),
end: propertyAssignment.name.getEnd(),
replacement: replacedPropertyName
}
return "require('" + url + "')";
});
].concat(styleUrlsReplacements);
}

module.exports = function(source, sourcemap) {
function constructUrlReplacement(element) {
var delimiter = "'";
if (element.kind === ts.SyntaxKind.FirstTemplateToken) {
delimiter = "`";
}
var url = element.text.replace(/(['"\\])/g, "\\$&");
if (url.charAt(0) !== ".") {
url = "./" + url;
}

return {
start: element.getStart(),
end: element.getEnd(),
replacement: "require(" + delimiter + url + delimiter + ")"
};
}

function isTemplateUrlProperty(propertyAssignment) {
return (
propertyAssignment.name.kind === ts.SyntaxKind.Identifier &&
propertyAssignment.name.text === "templateUrl" &&
(propertyAssignment.initializer.kind === ts.SyntaxKind.StringLiteral ||
propertyAssignment.initializer.kind === ts.SyntaxKind.FirstTemplateToken)
);
}

function isStyleUrlsPropery(propertyAssignment) {
return (
propertyAssignment.name.kind === ts.SyntaxKind.Identifier &&
propertyAssignment.name.text === "styleUrls" &&
propertyAssignment.initializer.kind === ts.SyntaxKind.ArrayLiteralExpression &&
propertyAssignment.initializer.elements.every(function(element) {
return (
element.kind === ts.SyntaxKind.StringLiteral ||
element.kind === ts.SyntaxKind.FirstTemplateToken
);
})
);
}

function flatten(prev, current) {
return prev.concat(current);
}

function findeReplacements(node, templateReplacement, styleReplacement) {
var positions = [];

calculateReplacements(node);

function calculateReplacements(node) {
switch (node.kind) {
// search only for class declarations
case ts.SyntaxKind.ClassDeclaration:
if (node.decorators && node.decorators.length > 0) {
// filter for all decorators containing a component decorator
var replacementPositions = node.decorators
.filter(function(decorator) {
return (
decorator.expression.kind === ts.SyntaxKind.CallExpression &&
decorator.expression.expression.kind === ts.SyntaxKind.Identifier &&
decorator.expression.expression.text === "Component" &&
decorator.expression.arguments.length > 0
);
})
.map(function(decorator) {
return decorator.expression.arguments[0];
})
// the argument must be a literal expression
.filter(function(argument) {
return (argument.kind = ts.SyntaxKind.ObjectLiteralExpression);
})
.map(function(literalExpression) {
return literalExpression.properties;
})
.reduce(flatten, [])
// filter for property assignments
.filter(function(properties) {
return properties.kind === ts.SyntaxKind.PropertyAssignment;
})
// only filter for property assignments with the text templateUrl or styleUrls
.filter(function(propertyAssignment) {
return (
isTemplateUrlProperty(propertyAssignment) || isStyleUrlsPropery(propertyAssignment)
);
})
.map(function(propertyAssignment) {
if (propertyAssignment.name.text === "templateUrl") {
return calculateTemplateUrlReplacement(propertyAssignment, templateReplacement);
} else {
return calculateStyleUrlsReplacement(propertyAssignment, styleReplacement);
}
})
.reduce(flatten, []);
positions = positions.concat(replacementPositions);
}
break;
}

return ts.forEachChild(node, calculateReplacements);
}

return positions;
}

module.exports = function(source, sourcemap) {
var config = {};
var query = loaderUtils.parseQuery(this.query);
var styleProperty = 'styles';
var templateProperty = 'template';
var styleProperty = "styles";
var templateProperty = "template";

if (this.options != null) {
Object.assign(config, this.options['angular2TemplateLoader']);
Object.assign(config, this.options["angular2TemplateLoader"]);
}

Object.assign(config, query);

if (config.keepUrl === true) {
styleProperty = 'styleUrls';
templateProperty = 'templateUrl';
styleProperty = "styleUrls";
templateProperty = "templateUrl";
}

// Not cacheable during unit tests;
// Not cacheable during unit tests;
this.cacheable && this.cacheable();

var newSource = source.replace(templateUrlRegex, function (match, url) {
// replace: templateUrl: './path/to/template.html'
// with: template: require('./path/to/template.html')
// or: templateUrl: require('./path/to/template.html')
// if `keepUrl` query parameter is set to true.
return templateProperty + ":" + replaceStringsWithRequires(url);
})
.replace(stylesRegex, function (match, urls) {
// replace: stylesUrl: ['./foo.css', "./baz.css", "./index.component.css"]
// with: styles: [require('./foo.css'), require("./baz.css"), require("./index.component.css")]
// or: styleUrls: [require('./foo.css'), require("./baz.css"), require("./index.component.css")]
// if `keepUrl` query parameter is set to true.
return styleProperty + ":" + replaceStringsWithRequires(urls);
});
var fileName = this.resourcePath;

var sourceFile = ts.createSourceFile(
fileName,
source,
ts.ScriptTarget.ES6,
/*setParentNodes */ true
);

var positions = findeReplacements(sourceFile, templateProperty, styleProperty);

var newSource = source;

for (var i = positions.length - 1; i >= 0; i--) {
var pos = positions[i];
var prefix = newSource.substring(0, pos.start);
var postfix = newSource.substring(pos.end);
newSource = prefix + pos.replacement + postfix;
}

// Support for tests
if (this.callback) {
this.callback(null, newSource, sourcemap)
this.callback(null, newSource, sourcemap);
} else {
return newSource;
}
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"should": "^9.0.0"
},
"dependencies": {
"loader-utils": "^0.2.15"
"loader-utils": "^0.2.15",
"typescript": "^2.3.4"
}
}
12 changes: 12 additions & 0 deletions test/fixtures/component_with_template_literals.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
var componentWithTemplateLiterals = `
import {Component} from '@angular/core';

@Component({
selector: 'test-component',
templateUrl: \`./some/path/to/file.html\`,
styleUrls: [\`./app/css/styles.css\`]
})
export class TestComponent {}
`;

module.exports = componentWithTemplateLiterals;
2 changes: 2 additions & 0 deletions test/fixtures/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ var componentWithoutRelPeriodSlash = require('./component_without_relative_perio
var componentWithSpacing = require('./component_with_spacing.js');
var componentWithSingleLineDecorator = require('./component_with_single_line_decorator.js');
var componentWithTemplateUrlEndingBySpace = require('./component_with_template_url_ending_by_space.js');
var componentWithTemplateLiterals = require('./component_with_template_literals.js');

exports.simpleAngularTestComponentFileStringSimple = sampleAngularComponentSimpleFixture;
exports.componentWithQuoteInUrls = componentWithQuoteInUrls;
Expand All @@ -13,3 +14,4 @@ exports.componentWithoutRelPeriodSlash = componentWithoutRelPeriodSlash;
exports.componentWithSpacing = componentWithSpacing;
exports.componentWithSingleLineDecorator = componentWithSingleLineDecorator;
exports.componentWithTemplateUrlEndingBySpace = componentWithTemplateUrlEndingBySpace;
exports.componentWithTemplateLiterals = componentWithTemplateLiterals;
Loading