From 9966d09513b1bb289f1d98044dfca8b09e8d3495 Mon Sep 17 00:00:00 2001 From: EddyVerbruggen Date: Tue, 16 May 2017 19:52:32 +0200 Subject: [PATCH 1/3] Implemented platform-specific views --- index.js | 1 + .../ViewUrlResolvePlugin.js | 97 +++++++++++++++++++ templates/webpack.angular.js | 2 + 3 files changed, 100 insertions(+) create mode 100644 resource-resolver-plugins/ViewUrlResolvePlugin.js diff --git a/index.js b/index.js index 20f86ed7..35ed93a2 100644 --- a/index.js +++ b/index.js @@ -13,6 +13,7 @@ var isAngular = Object.keys(packageJson.dependencies).filter(function (dependenc if (isAngular) { exports.StyleUrlResolvePlugin = require("./resource-resolver-plugins/StyleUrlResolvePlugin"); + exports.ViewUrlResolvePlugin = require("./resource-resolver-plugins/ViewUrlResolvePlugin"); } //HACK: changes the JSONP chunk eval function to `global["nativescriptJsonp"]` diff --git a/resource-resolver-plugins/ViewUrlResolvePlugin.js b/resource-resolver-plugins/ViewUrlResolvePlugin.js new file mode 100644 index 00000000..91cf5f4f --- /dev/null +++ b/resource-resolver-plugins/ViewUrlResolvePlugin.js @@ -0,0 +1,97 @@ +const ts = require("typescript"); +const fs = require("fs"); +const path = require("path"); + +const ViewUrlResolvePlugin = (function() { + function ViewUrlResolvePlugin(options) { + if (!options || !options.platform) { + throw new Error(`Target platform must be specified!`); + } + + this.platform = options.platform; + } + + ViewUrlResolvePlugin.prototype.apply = function (compiler) { + compiler.plugin("make", (compilation, callback) => { + const aotPlugin = getAotPlugin(compilation); + aotPlugin._program.getSourceFiles() + .forEach(sf => this.usePlatformViewUrl(sf)); + + callback(); + }) + }; + + function getAotPlugin(compilation) { + let maybeAotPlugin = compilation._ngToolsWebpackPluginInstance; + if (!maybeAotPlugin) { + throw new Error(`This plugin must be used with the AotPlugin!`); + } + + return maybeAotPlugin; + } + + ViewUrlResolvePlugin.prototype.usePlatformViewUrl = function(sourceFile) { + this.setCurrentDirectory(sourceFile); + ts.forEachChild(sourceFile, node => this.traverseDecorators(node)); + } + + ViewUrlResolvePlugin.prototype.setCurrentDirectory = function(sourceFile) { + this.currentDirectory = path.resolve(sourceFile.path, ".."); + } + + ViewUrlResolvePlugin.prototype.traverseDecorators = function(node) { + if (node.kind !== ts.SyntaxKind.ClassDeclaration || !node.decorators) { + return; + } + + node.decorators.forEach(decorator => { + this.traverseDecoratorArguments(decorator.expression.arguments); + }); + } + + ViewUrlResolvePlugin.prototype.traverseDecoratorArguments = function(args) { + args.forEach(arg => arg.properties && this.traverseProperties(arg.properties)); + } + + ViewUrlResolvePlugin.prototype.traverseProperties = function(properties) { + properties.filter(isTemplateUrl) + .forEach(prop => this.traversePropertyElements(prop)); + } + + function isTemplateUrl(property) { + return property.name.text === "templateUrl"; + } + + ViewUrlResolvePlugin.prototype.traversePropertyElements = function(property) { + [property.initializer] + .filter(el => this.notPlatformUrl(el.text)) + .filter(el => this.noMultiplatformFile(el.text)) + .forEach(el => this.replaceViewUrlValue(el)); + } + + ViewUrlResolvePlugin.prototype.notPlatformUrl = function(viewUrl) { + let extensionStartIndex = viewUrl.lastIndexOf("."); + let extension = viewUrl.slice(extensionStartIndex); + + + return !viewUrl.endsWith(`.${this.platform}${extension}`); + } + + ViewUrlResolvePlugin.prototype.noMultiplatformFile = function(viewUrl) { + let viewPath = path.resolve(this.currentDirectory, viewUrl); + + return !fs.existsSync(viewPath); + } + + ViewUrlResolvePlugin.prototype.replaceViewUrlValue = function(element) { + const extensionStartIndex = element.text.lastIndexOf("."); + const prefix = element.text.slice(0, extensionStartIndex); + const currentExtension = element.text.slice(extensionStartIndex); + + element.text = `${prefix}.${this.platform}${currentExtension}`; + } + + return ViewUrlResolvePlugin; +})(); + +module.exports = ViewUrlResolvePlugin; diff --git a/templates/webpack.angular.js b/templates/webpack.angular.js index c1069373..e543704a 100644 --- a/templates/webpack.angular.js +++ b/templates/webpack.angular.js @@ -163,6 +163,8 @@ function getPlugins(platform, env) { // Resolve .ios.css and .android.css component stylesheets new nsWebpack.StyleUrlResolvePlugin({platform}), + // Resolve .ios.html and .android.html component views + new nsWebpack.ViewUrlResolvePlugin({platform}), ]; if (env.uglify) { From 45ebadb89c06d52894b90b6244fab33cafa3d3ca Mon Sep 17 00:00:00 2001 From: EddyVerbruggen Date: Fri, 26 May 2017 18:20:20 +0200 Subject: [PATCH 2/3] Refactored to reuse as much code as possible, and offer the option to opt out of (the default) platform replacement strategy. --- index.js | 3 +- prepublish/angular/plugins.js | 8 +- .../StyleUrlResolvePlugin.js | 97 ---------------- resource-resolver-plugins/UrlResolvePlugin.js | 109 ++++++++++++++++++ .../ViewUrlResolvePlugin.js | 97 ---------------- templates/webpack.angular.js | 11 +- 6 files changed, 122 insertions(+), 203 deletions(-) delete mode 100644 resource-resolver-plugins/StyleUrlResolvePlugin.js create mode 100644 resource-resolver-plugins/UrlResolvePlugin.js delete mode 100644 resource-resolver-plugins/ViewUrlResolvePlugin.js diff --git a/index.js b/index.js index 35ed93a2..5557ec39 100644 --- a/index.js +++ b/index.js @@ -12,8 +12,7 @@ var isAngular = Object.keys(packageJson.dependencies).filter(function (dependenc if (isAngular) { - exports.StyleUrlResolvePlugin = require("./resource-resolver-plugins/StyleUrlResolvePlugin"); - exports.ViewUrlResolvePlugin = require("./resource-resolver-plugins/ViewUrlResolvePlugin"); + exports.UrlResolvePlugin = require("./resource-resolver-plugins/UrlResolvePlugin"); } //HACK: changes the JSONP chunk eval function to `global["nativescriptJsonp"]` diff --git a/prepublish/angular/plugins.js b/prepublish/angular/plugins.js index a09d8105..6d6402d0 100644 --- a/prepublish/angular/plugins.js +++ b/prepublish/angular/plugins.js @@ -6,6 +6,10 @@ module.exports = ` typeChecking: false }), - // Resolve .ios.css and .android.css component stylesheets - new nsWebpack.StyleUrlResolvePlugin({platform}), + // Resolve .ios.css and .android.css component stylesheets, and .ios.html and .android component views + new nsWebpack.UrlResolvePlugin({ + platform: platform, + resolveStylesUrls: true, + resolveTemplateUrl: true + }), `; diff --git a/resource-resolver-plugins/StyleUrlResolvePlugin.js b/resource-resolver-plugins/StyleUrlResolvePlugin.js deleted file mode 100644 index ee110adb..00000000 --- a/resource-resolver-plugins/StyleUrlResolvePlugin.js +++ /dev/null @@ -1,97 +0,0 @@ -const ts = require("typescript"); -const fs = require("fs"); -const path = require("path"); - -const StyleUrlResolvePlugin = (function() { - function StyleUrlResolvePlugin(options) { - if (!options || !options.platform) { - throw new Error(`Target platform must be specified!`); - } - - this.platform = options.platform; - } - - StyleUrlResolvePlugin.prototype.apply = function (compiler) { - compiler.plugin("make", (compilation, callback) => { - const aotPlugin = getAotPlugin(compilation); - aotPlugin._program.getSourceFiles() - .forEach(sf => this.usePlatformStyleUrl(sf)); - - callback(); - }) - }; - - function getAotPlugin(compilation) { - let maybeAotPlugin = compilation._ngToolsWebpackPluginInstance; - if (!maybeAotPlugin) { - throw new Error(`This plugin must be used with the AotPlugin!`); - } - - return maybeAotPlugin; - } - - StyleUrlResolvePlugin.prototype.usePlatformStyleUrl = function(sourceFile) { - this.setCurrentDirectory(sourceFile); - ts.forEachChild(sourceFile, node => this.traverseDecorators(node)); - } - - StyleUrlResolvePlugin.prototype.setCurrentDirectory = function(sourceFile) { - this.currentDirectory = path.resolve(sourceFile.path, ".."); - } - - StyleUrlResolvePlugin.prototype.traverseDecorators = function(node) { - if (node.kind !== ts.SyntaxKind.ClassDeclaration || !node.decorators) { - return; - } - - node.decorators.forEach(decorator => { - this.traverseDecoratorArguments(decorator.expression.arguments); - }); - } - - StyleUrlResolvePlugin.prototype.traverseDecoratorArguments = function(args) { - args.forEach(arg => arg.properties && this.traverseProperties(arg.properties)); - } - - StyleUrlResolvePlugin.prototype.traverseProperties = function(properties) { - properties.filter(isStyleUrls) - .forEach(prop => this.traversePropertyElements(prop)); - } - - function isStyleUrls(property) { - return property.name.text === "styleUrls"; - } - - StyleUrlResolvePlugin.prototype.traversePropertyElements = function(property) { - property.initializer.elements - .filter(el => !!el.text) - .filter(el => this.notPlatformUrl(el.text)) - .filter(el => this.noMultiplatformFile(el.text)) - .forEach(el => this.replaceStyleUrlsValue(el)); - } - - StyleUrlResolvePlugin.prototype.notPlatformUrl = function(styleUrl) { - let extensionStartIndex = styleUrl.lastIndexOf("."); - let extension = styleUrl.slice(extensionStartIndex); - - return !styleUrl.endsWith(`.${this.platform}${extension}`); - } - - StyleUrlResolvePlugin.prototype.noMultiplatformFile = function(styleUrl) { - let stylePath = path.resolve(this.currentDirectory, styleUrl); - - return !fs.existsSync(stylePath); - } - - StyleUrlResolvePlugin.prototype.replaceStyleUrlsValue = function(element) { - const extensionStartIndex = element.text.lastIndexOf("."); - const prefix = element.text.slice(0, extensionStartIndex); - const currentExtension = element.text.slice(extensionStartIndex); - - element.text = `${prefix}.${this.platform}${currentExtension}`; - } - - return StyleUrlResolvePlugin; -})(); - -module.exports = StyleUrlResolvePlugin; diff --git a/resource-resolver-plugins/UrlResolvePlugin.js b/resource-resolver-plugins/UrlResolvePlugin.js new file mode 100644 index 00000000..1222d695 --- /dev/null +++ b/resource-resolver-plugins/UrlResolvePlugin.js @@ -0,0 +1,109 @@ +const ts = require("typescript"); +const fs = require("fs"); +const path = require("path"); + +const UrlResolvePlugin = (function() { + function UrlResolvePlugin(options) { + if (!options || !options.platform) { + throw new Error(`Target platform must be specified!`); + } + + this.platform = options.platform; + + // these are true by default + this.resolveStylesUrls = options.resolveStylesUrls === undefined || options.resolveStylesUrls; + this.resolveTemplateUrl = options.resolveTemplateUrl === undefined || options.resolveTemplateUrl; + + if (!this.resolveStylesUrls && !this.resolveTemplateUrl) { + throw new Error(`resolveStylesUrls and resolveTemplateUrl mustn't both be false`); + } + } + + UrlResolvePlugin.prototype.apply = function (compiler) { + compiler.plugin("make", (compilation, callback) => { + const aotPlugin = getAotPlugin(compilation); + aotPlugin._program.getSourceFiles() + .forEach(sf => this.usePlatformUrl(sf)); + + callback(); + }) + }; + + function getAotPlugin(compilation) { + let maybeAotPlugin = compilation._ngToolsWebpackPluginInstance; + if (!maybeAotPlugin) { + throw new Error(`This plugin must be used with the AotPlugin!`); + } + + return maybeAotPlugin; + } + + UrlResolvePlugin.prototype.usePlatformUrl = function(sourceFile) { + this.setCurrentDirectory(sourceFile); + ts.forEachChild(sourceFile, node => this.traverseDecorators(node)); + } + + UrlResolvePlugin.prototype.setCurrentDirectory = function(sourceFile) { + this.currentDirectory = path.resolve(sourceFile.path, ".."); + } + + UrlResolvePlugin.prototype.traverseDecorators = function(node) { + if (node.kind !== ts.SyntaxKind.ClassDeclaration || !node.decorators) { + return; + } + + node.decorators.forEach(decorator => { + this.traverseDecoratorArguments(decorator.expression.arguments); + }); + } + + UrlResolvePlugin.prototype.traverseDecoratorArguments = function(args) { + args.forEach(arg => arg.properties && this.traverseProperties(arg.properties)); + } + + UrlResolvePlugin.prototype.traverseProperties = function(properties) { + properties + .filter(prop => this.isRelevantNode(prop)) + .forEach(prop => this.traversePropertyElements(prop)); + } + + UrlResolvePlugin.prototype.isRelevantNode = function(property) { + return this.resolveStylesUrls && property.name.text === "styleUrls" || + this.resolveTemplateUrl && property.name.text === "templateUrl" + } + + UrlResolvePlugin.prototype.traversePropertyElements = function(property) { + const elements = property.initializer.elements === undefined ? [property.initializer] : property.initializer.elements; + + elements + .filter(el => !!el.text) + .filter(el => this.notPlatformUrl(el.text)) + .filter(el => this.noMultiplatformFile(el.text)) + .forEach(el => this.replaceUrlsValue(el)); + } + + UrlResolvePlugin.prototype.notPlatformUrl = function(url) { + let extensionStartIndex = url.lastIndexOf("."); + let extension = url.slice(extensionStartIndex); + + return !url.endsWith(`.${this.platform}${extension}`); + } + + UrlResolvePlugin.prototype.noMultiplatformFile = function(url) { + let filePath = path.resolve(this.currentDirectory, url); + + return !fs.existsSync(filePath); + } + + UrlResolvePlugin.prototype.replaceUrlsValue = function(element) { + const extensionStartIndex = element.text.lastIndexOf("."); + const prefix = element.text.slice(0, extensionStartIndex); + const currentExtension = element.text.slice(extensionStartIndex); + + element.text = `${prefix}.${this.platform}${currentExtension}`; + } + + return UrlResolvePlugin; +})(); + +module.exports = UrlResolvePlugin; diff --git a/resource-resolver-plugins/ViewUrlResolvePlugin.js b/resource-resolver-plugins/ViewUrlResolvePlugin.js deleted file mode 100644 index 91cf5f4f..00000000 --- a/resource-resolver-plugins/ViewUrlResolvePlugin.js +++ /dev/null @@ -1,97 +0,0 @@ -const ts = require("typescript"); -const fs = require("fs"); -const path = require("path"); - -const ViewUrlResolvePlugin = (function() { - function ViewUrlResolvePlugin(options) { - if (!options || !options.platform) { - throw new Error(`Target platform must be specified!`); - } - - this.platform = options.platform; - } - - ViewUrlResolvePlugin.prototype.apply = function (compiler) { - compiler.plugin("make", (compilation, callback) => { - const aotPlugin = getAotPlugin(compilation); - aotPlugin._program.getSourceFiles() - .forEach(sf => this.usePlatformViewUrl(sf)); - - callback(); - }) - }; - - function getAotPlugin(compilation) { - let maybeAotPlugin = compilation._ngToolsWebpackPluginInstance; - if (!maybeAotPlugin) { - throw new Error(`This plugin must be used with the AotPlugin!`); - } - - return maybeAotPlugin; - } - - ViewUrlResolvePlugin.prototype.usePlatformViewUrl = function(sourceFile) { - this.setCurrentDirectory(sourceFile); - ts.forEachChild(sourceFile, node => this.traverseDecorators(node)); - } - - ViewUrlResolvePlugin.prototype.setCurrentDirectory = function(sourceFile) { - this.currentDirectory = path.resolve(sourceFile.path, ".."); - } - - ViewUrlResolvePlugin.prototype.traverseDecorators = function(node) { - if (node.kind !== ts.SyntaxKind.ClassDeclaration || !node.decorators) { - return; - } - - node.decorators.forEach(decorator => { - this.traverseDecoratorArguments(decorator.expression.arguments); - }); - } - - ViewUrlResolvePlugin.prototype.traverseDecoratorArguments = function(args) { - args.forEach(arg => arg.properties && this.traverseProperties(arg.properties)); - } - - ViewUrlResolvePlugin.prototype.traverseProperties = function(properties) { - properties.filter(isTemplateUrl) - .forEach(prop => this.traversePropertyElements(prop)); - } - - function isTemplateUrl(property) { - return property.name.text === "templateUrl"; - } - - ViewUrlResolvePlugin.prototype.traversePropertyElements = function(property) { - [property.initializer] - .filter(el => this.notPlatformUrl(el.text)) - .filter(el => this.noMultiplatformFile(el.text)) - .forEach(el => this.replaceViewUrlValue(el)); - } - - ViewUrlResolvePlugin.prototype.notPlatformUrl = function(viewUrl) { - let extensionStartIndex = viewUrl.lastIndexOf("."); - let extension = viewUrl.slice(extensionStartIndex); - - - return !viewUrl.endsWith(`.${this.platform}${extension}`); - } - - ViewUrlResolvePlugin.prototype.noMultiplatformFile = function(viewUrl) { - let viewPath = path.resolve(this.currentDirectory, viewUrl); - - return !fs.existsSync(viewPath); - } - - ViewUrlResolvePlugin.prototype.replaceViewUrlValue = function(element) { - const extensionStartIndex = element.text.lastIndexOf("."); - const prefix = element.text.slice(0, extensionStartIndex); - const currentExtension = element.text.slice(extensionStartIndex); - - element.text = `${prefix}.${this.platform}${currentExtension}`; - } - - return ViewUrlResolvePlugin; -})(); - -module.exports = ViewUrlResolvePlugin; diff --git a/templates/webpack.angular.js b/templates/webpack.angular.js index e543704a..51590bb6 100644 --- a/templates/webpack.angular.js +++ b/templates/webpack.angular.js @@ -160,11 +160,12 @@ function getPlugins(platform, env) { typeChecking: false }), - // Resolve .ios.css and .android.css component stylesheets - new nsWebpack.StyleUrlResolvePlugin({platform}), - - // Resolve .ios.html and .android.html component views - new nsWebpack.ViewUrlResolvePlugin({platform}), + // // Resolve .ios.css and .android.css component stylesheets, and .ios.html and .android component views + new nsWebpack.UrlResolvePlugin({ + platform: platform, + resolveStylesUrls: true, + resolveTemplateUrl: true + }), ]; if (env.uglify) { From 60466be891e5984ca4252a0eb4bd58cf1f45d587 Mon Sep 17 00:00:00 2001 From: sis0k0 Date: Mon, 29 May 2017 10:56:15 +0300 Subject: [PATCH 3/3] feat: postinstall replaces StyleUrlResolvePlugin with UrlResolvePlugin --- installer.js | 1 + projectFilesManager.js | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/installer.js b/installer.js index 9cb2e197..386f74db 100644 --- a/installer.js +++ b/installer.js @@ -13,6 +13,7 @@ function install() { let packageJson = helpers.getPackageJson(PROJECT_DIR); projectFilesManager.addProjectFiles(PROJECT_DIR, APP_DIR); + projectFilesManager.editExistingProjectFiles(PROJECT_DIR); let scripts = packageJson.scripts || {}; scripts = npmScriptsManager.removeDeprecatedNpmScripts(scripts); diff --git a/projectFilesManager.js b/projectFilesManager.js index a2ce223e..b7c298ea 100644 --- a/projectFilesManager.js +++ b/projectFilesManager.js @@ -91,6 +91,29 @@ function getFullTemplatesPath(projectDir, templates) { return updatedTemplates; } +function editExistingProjectFiles(projectDir) { + const webpackConfigPath = getFullPath(projectDir, "webpack.config.js"); + const webpackCommonPath = getFullPath(projectDir, "webpack.common.js"); + + editWebpackConfig(webpackConfigPath, replaceStyleUrlResolvePlugin); + editWebpackConfig(webpackCommonPath, replaceStyleUrlResolvePlugin); +} + +function editWebpackConfig(path, fn) { + if (!fs.existsSync(path)) { + return; + } + + const config = fs.readFileSync(path, "utf8"); + const newConfig = fn(config); + + fs.writeFileSync(path, newConfig, "utf8"); +} + +function replaceStyleUrlResolvePlugin(config) { + return config.replace(/StyleUrlResolvePlugin/g, "UrlResolvePlugin"); +} + function getFullPath(projectDir, filePath) { return path.resolve(projectDir, filePath); } @@ -103,4 +126,5 @@ function tsOrJs(projectDir, name) { module.exports = { addProjectFiles, removeProjectFiles, + editExistingProjectFiles, };