From 7b5e9acbb605501d6170cc5e32ea2cded5bf430a Mon Sep 17 00:00:00 2001 From: Vladimir Enchev Date: Thu, 22 Dec 2016 11:48:19 +0200 Subject: [PATCH] ExcludeUnusedElements plugin + loader added --- index.js | 53 +++++++++++++++++++++++++----- postinstall.js | 1 + tns-xml-loader.js | 41 +++++++++++++++++++++++ webpack.common.js.angular.template | 12 +++++-- 4 files changed, 96 insertions(+), 11 deletions(-) create mode 100644 tns-xml-loader.js diff --git a/index.js b/index.js index da303583..1253ddc5 100644 --- a/index.js +++ b/index.js @@ -17,7 +17,7 @@ if (isAngular) { //HACK: changes the JSONP chunk eval function to `global["nativescriptJsonp"]` // applied to tns-java-classes.js only -exports.NativeScriptJsonpPlugin = function(options) { +exports.NativeScriptJsonpPlugin = function (options) { }; exports.NativeScriptJsonpPlugin.prototype.apply = function (compiler) { @@ -40,7 +40,42 @@ exports.NativeScriptJsonpPlugin.prototype.apply = function (compiler) { }); }; -exports.GenerateBundleStarterPlugin = function(bundles) { +exports.ExcludeUnusedElementsPlugin = function () { +}; + +exports.ExcludeUnusedElementsPlugin.prototype.apply = function (compiler) { + compiler.plugin("normal-module-factory", function (nmf) { + nmf.plugin("before-resolve", function (result, callback) { + if (!result) { + return callback(); + } + + if (result.request === "globals" || result.request === "ui/core/view") { + return callback(null, result); + } + + if (result.context.indexOf("tns-core-modules") === -1) { + if (result.contextInfo.issuer && + result.contextInfo.issuer.indexOf("element-registry") !== -1 && global["ELEMENT_REGISTRY"] && + !global["ELEMENT_REGISTRY"][result.request]) { + return callback(); + + } else { + return callback(null, result); + } + } + + if (result.contextInfo.issuer.indexOf("bundle-entry-points") !== -1 && global["ELEMENT_REGISTRY"] && + !global["ELEMENT_REGISTRY"][result.request]) { + return callback(); + } + + return callback(null, result); + }); + }); +}; + +exports.GenerateBundleStarterPlugin = function (bundles) { this.bundles = bundles; }; @@ -58,22 +93,22 @@ exports.GenerateBundleStarterPlugin.prototype = { cb(); }); }, - generatePackageJson: function() { + generatePackageJson: function () { var packageJsonPath = path.join(this.webpackContext, "package.json"); var packageData = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")); packageData.main = "starter"; return new sources.RawSource(JSON.stringify(packageData, null, 4)); }, - generateStarterModule: function() { - var moduleSource = this.bundles.map(function(bundle) { + generateStarterModule: function () { + var moduleSource = this.bundles.map(function (bundle) { return "require(\"" + bundle + "\");"; }).join("\n"); return new sources.RawSource(moduleSource); }, }; -exports.getEntryModule = function() { +exports.getEntryModule = function () { const maybePackageJsonEntry = getPackageJsonEntry(); if (!maybePackageJsonEntry) { throw new Error("app/package.json must contain a `main` attribute."); @@ -83,12 +118,12 @@ exports.getEntryModule = function() { return maybeAotEntry || maybePackageJsonEntry; }; -exports.getAppPath = function(platform) { +exports.getAppPath = function (platform) { var projectDir = path.dirname(path.dirname(__dirname)); if (/ios/i.test(platform)) { var appName = path.basename(projectDir); - var sanitizedName = appName.split("").filter(function(c) { + var sanitizedName = appName.split("").filter(function (c) { return /[a-zA-Z0-9]/.test(c); }).join(""); return "platforms/ios/" + sanitizedName + "/app"; @@ -127,7 +162,7 @@ function getPackageJsonEntry() { const entry = packageJsonSource.main; return entry ? entry.replace(/\.js$/i, "") : null; - } +} function getAppPackageJsonSource() { const projectDir = getProjectDir(); diff --git a/postinstall.js b/postinstall.js index de88b6d6..31069897 100644 --- a/postinstall.js +++ b/postinstall.js @@ -56,6 +56,7 @@ configureDevDependencies(packageJson, function (add) { add("@angular/compiler-cli", "2.3.1"); add("@ngtools/webpack", "1.2.1"); add("typescript", "~2.0.10"); + add("htmlparser2", "^3.9.2"); } else { add("awesome-typescript-loader", "~3.0.0-beta.9"); } diff --git a/tns-xml-loader.js b/tns-xml-loader.js new file mode 100644 index 00000000..4e5b347e --- /dev/null +++ b/tns-xml-loader.js @@ -0,0 +1,41 @@ +var htmlparser = require("htmlparser2"); + +var UI_PATH = "ui/"; + +var MODULES = { + "TabViewItem": "ui/tab-view", + "FormattedString": "text/formatted-string", + "Span": "text/span", + "ActionItem": "ui/action-bar", + "NavigationButton": "ui/action-bar", + "SegmentedBarItem": "ui/segmented-bar", +}; + +var ELEMENT_REGISTRY = "ELEMENT_REGISTRY"; + +if (!global[ELEMENT_REGISTRY]) { + global[ELEMENT_REGISTRY] = {}; +} + +module.exports = function (source, map) { + this.cacheable(); + + var loader = this; + + var parser = new htmlparser.Parser({ + onopentag: function (name, attribs) { + // kebab-case to CamelCase + var elementName = name.split("-").map(function (s) { return s[0].toUpperCase() + s.substring(1); }).join(""); + // Module path from element name + var modulePath = MODULES[elementName] || UI_PATH + + (elementName.toLowerCase().indexOf("layout") !== -1 ? "layouts/" : "") + + elementName.split(/(?=[A-Z])/).join("-").toLowerCase(); + // Update ELEMENT_REGISTRY + global[ELEMENT_REGISTRY][modulePath] = elementName; + } + }, { decodeEntities: true, lowerCaseTags: false }); + parser.write(source); + parser.end(); + + this.callback(null, source, map); +}; \ No newline at end of file diff --git a/webpack.common.js.angular.template b/webpack.common.js.angular.template index 43edb69b..e74de172 100644 --- a/webpack.common.js.angular.template +++ b/webpack.common.js.angular.template @@ -45,6 +45,11 @@ module.exports = function (platform, destinationApp) { "./vendor", "./bundle", ]), + + // Exclude explicitly required but never declared in XML elements. + // Loader nativescript-dev-webpack/tns-xml-loader should be added for *.xml/html files. + new nsWebpack.ExcludeUnusedElementsPlugin(), + //Angular AOT compiler new AotPlugin({ tsConfigPath: "tsconfig.aot.json", @@ -101,8 +106,11 @@ module.exports = function (platform, destinationApp) { module: { loaders: [ { - test: /\.html$/, - loader: "raw-loader" + test: /\.html$|\.xml$/, + loaders: [ + "raw-loader", + 'nativescript-dev-webpack/tns-xml-loader' + ] }, // Root app.css file gets extracted with bundled dependencies {