返回博客首页
← 所有文章

升级到 NativeScript Webpack 0.12.0

2018年6月4日 — 作者 Stanimira Vlaeva

NativeScript 4.1 版本包含了对 **nativescript-dev-webpack** 插件的更新。插件的新版本为 0.12.0,并带来了一些重要的更改。

不要错过涵盖 {N} 4.0/4.1 以及 Angular 和 Vue.js 代码共享策略的 下一场 NativeScript 网络研讨会

Webpack 4 和 Angular 6

该插件现在需要 webpack v4+、Angular v6+ 和 {N} v4+。

更快的 Android 启动速度

扩展的 UglifyJS 对 Android 的支持以及改进的快照包生成导致了更小的包大小和更快的启动时间。加上新的超快速 Android 运行时,每个我们测量的应用程序的总启动时间提高了 **~50%**。请分享您应用程序的结果!下表显示了一些 NativeScript 应用程序的之前和现在的启动时间。它们是在发布模式下构建的,启用了提前编译、缩小和快照。

Android
运行时 **4.0**
{N}-webpack **0.11.0**
Android
运行时 **4.1**
{N}-webpack **0.11.0**
Android
运行时 **4.1**
{N}-webpack **0.12.0**
改进
应用:Groceries
设备:Pixel 2
1467ms 1156ms 764ms 51.76%
应用:Hello world Angular
设备:Pixel 2
1277ms 976ms 600ms 46.99%
应用:SDK 示例
设备:Nexus 5x(比 Pixel 2 慢)
2301ms 1816ms 1333ms 57.93%


更简单的设置

对于非 Angular 项目,您需要手动配置一些内容才能使 webpack 工作。通常,您会在项目中添加一个 **bundle-config.js** 文件来引入 xml 页面、注册外部 UI 插件、加载 css 等。

您不再需要这样做,并且可以安全地删除 **bundle-config** 文件,因为 **nativescript-dev-webpack** 插件将为您配置以上所有内容。该插件通过几个 webpack 加载器实现这一点,这些加载器现在是您 webpack 配置的一部分。我们将在接下来的部分中介绍每个加载器。

依赖项

如上所述,该插件需要 webpack 4。显然,为了升级到 webpack 4,您需要做的第一件事是……将您的 webpack 依赖项更新到版本 4。但是,如果您正在开发 {N} 应用程序,则您可能正在使用大量 webpack 加载器和插件。如果您的应用程序使用 Angular 构建,您还需要更新您的 Angular 包。以下是一些您可以遵循的步骤来自动化更新过程

  1. [仅限 Angular] 更新 nativescript-angular
  2. npm i [email protected]
  3. [仅限 Angular] 更新您的 @angular/* 包。nativescript-angular 包中分发了用于执行此操作的脚本
    ./node_modules/.bin/update-app-ng-deps
  4. [仅限 Angular] 您必须将代码迁移到 Angular 6。这将对您有很大帮助 - https://update.angular.io

  5. 更新 nativescript-dev-webpack

    npm i --save-dev [email protected]
  6. 通过运行 nativescript-dev-webpack 包中分发的特殊脚本,更新您的 webpack 插件和加载器
    ./node_modules/.bin/update-ns-webpack --deps

  7. 确保您使用的是 {N} 运行时和 tns-core-modules 4.0 或更高版本。如果可能,请升级到 4.1 以利用 Android 运行时中令人惊叹的性能改进以及该版本中的所有新功能。

    // package.json
    {
      "name": "MyAwesomeProject",
      "version": "0.0.0",
      "nativescript": {
        "id": "org.nativescript.myawesomeproject",
        "tns-ios": {
          "version": "4.1.0"
        },
        "tns-android": {
          "version": "4.1.0"
        }

      },
      "dependencies": {
        // ...
        "nativescript-angular": "~6.0.0",
        "nativescript-theme-core": "~1.0.4",
        "tns-core-modules": "^4.1.0",
        "zone.js": "~0.8.2"
      }
    }

Webpack 配置

插件分发的 webpack.config.js 文件与之前的文件有很大不同。如果您进行了手动配置,请确保创建备份。然后预生成配置并将您的更改应用于它。

# back up your modified config
mv webpack.config.js webpack.config.js.bak
# get the latest version added to your project ./node_modules/.bin/update-ns-webpack --configs
# find the differences and apply the manual changes that you need diff webpack.config.js webpack.config.js.bak

EcmaScript 模块

使用 webpack 构建的所有 TypeScript 和 Angular 应用程序现在都使用目标 EcmaScript 模块。这允许 webpack 从您的代码中删除所有未使用的导出并提高最终的包大小。
当您安装 **nativescript-dev-webpack** 插件时,一个特殊的 **tsconfig.esm.json** 文件(指定该文件)将添加到您的项目中。它看起来像这样


// tsconfig.esm.json
{
   "extends": "./tsconfig",
   "compilerOptions": {
       "module": "es2015",
       "moduleResolution": "node"
   }
}

这引入了重大更改。在以 ES2015 模块为目标时,您不能使用导入赋值。如果您看到以下错误

ERROR in app/calendar/calendar-localization/calendar-localization.component.ts(2,1): error TS1202: 在以 ECMAScript 模块为目标时,不能使用导入赋值。请考虑使用“import * as ns from "mod"”、“import {a} from "mod"”、“import d from "mod"”或其他模块格式。

您需要以以下方式迁移导入赋值

之前

import buttonModule = require("tns-core-modules/ui/button");
const testButton = new buttonModule.Button();

之后


import * as buttonModule from "tns-core-modules/ui/button";
const testButton = new buttonModule.Button();

或者更好


import { Button } from "tns-core-modules/ui/button";
const testButton = new Button();


仅导入 **Button** 类将允许 webpack 从 **tns-core-modules/ui/button** 模块中剔除所有未使用的代码。这将导致更小的应用程序大小和更快的启动时间。

分割块插件

**CommonsChunkPlugin** 在 webpack 4 中已被 **SplitChunksPlugin** 取代。我们更改了生成块的方式以符合新的 webpack 分割策略。

  1. 您在应用程序中使用的所有外部包和自定义 Android 应用程序组件都提取到 vendor.js 块中。
  2. 因为所有外部包都在公共块中,所以您不必在 app/vendor.ts 中添加显式引入。这就是为什么 app/vendor.tsapp/vendor-platform.android.tsapp/vendor-platform.ios.ts 不见了的原因。如果您的应用程序中有它们,您可以删除它们。
  3. 如果您希望其他内容成为 vendor.js 块的一部分,请修改 webpack.config.js 中的 splitChunks 配置。

快照生成更改和自定义

vendor.js 生成中的更改也会影响快照插件。很酷的是,现在您开箱即用地拥有了所有 NativeScript 插件的快照,并且不必担心它们是否在内部具有本机 API 访问权限。这是因为它们没有执行。如果您想在启动时显式执行某些插件,则需要将其添加到 NativeScriptSnapshotPlugin 配置中 **requireModules** 数组中。这仅对非常大的插件有益,您可能永远不需要这样做。例如,在为 NativeScript Angular 应用程序生成快照时,会执行主要的 Angular 包


// webpack.config.js
// ... new nsWebpack.NativeScriptSnapshotPlugin({
    chunk: "vendor",
    requireModules: [
        "reflect-metadata",
        "@angular/platform-browser",
        "@angular/core",
        "@angular/common",
        "@angular/router",
        "nativescript-angular/platform-static",
        "nativescript-angular/router",
    ],
    projectRoot,
    webpackConfig: config,
});

缩小改进

UglifyJS 的 compress 选项破坏了 Android 的 NativeScript 静态绑定生成器,并且已为此平台禁用。我们已经确定了原因,现在启用了该选项,这将导致更小的应用程序包和更好的启动时间。

包配置

如上所述,**nativescript-dev-webpack** 插件的新版本带有一些加载器,这些加载器减少了您使项目准备好 webpack 所需的手动步骤数量。

包配置加载器

**bundle-config-loader** 应用于入口模块,其作用是插入一些预定义的配置。它具有以下选项

  • registerPages <boolean>

默认值:true

使用正则表达式注册应用程序的 XML、CSS 和 JavaScript 页面。所有页面的资源都应该命名为以 rootpage 结尾。例如 - main-page.xmlmain-page.cssmain-page.js

对于 Angular 应用程序,此选项应为 **false**,因为其中的组件在 NgModules 中注册。

  • loadCss <boolean>

默认值:true

bundle.js 块(您的应用程序代码)中加载应用程序 css。如果使用快照构建,则应为 **false**,因为在这种情况下,快照插件将在为“快照”执行的块中加载应用程序 css。请注意,您需要 Angular 和非 Angular 项目的此选项。

有关插件的示例用法,您可以查看 Angular 项目非 Angular 项目 的默认 webpack 配置。

XML 命名空间加载器

**xml-namespace-loader** 是非 Angular 项目的另一个加载器。它解析您的 xml 模板并注册那里使用的所有自定义组件。
假设您在 **main-page.xml** 中使用 **RadSideDrawer**

<nsDrawer:RadSideDrawer xmlns:nsDrawer="nativescript-ui-sidedrawer">
<!-- some content... --> 
</nsDrawer:RadSideDrawer>

之前,您必须注册 **nativescript-ui-sidedrawer** 模块

global.registerModule("nativescript-ui-sidedrawer",
() => require("nativescript-ui-sidedrawer"));

现在加载器将为您执行此操作。

自定义 Android 应用程序组件

NativeScript 允许您拥有自定义 Android 应用程序组件,例如 Activity 和服务(文档)。您应该使用 JavaScript 类扩展原始 Java 类,然后在 **AndroidManifest.xml** 文件中声明新创建的组件。

构建应用程序时,NativeScript 将对您的 JavaScript 代码执行静态分析并为该组件创建 Java 类。因此,当您使用 webpack 时,包含 JavaScript 扩展器的文件应成为包的一部分。

当您使用快照时,情况会变得稍微复杂一些。在这种情况下,包含 JavaScript 扩展器的模块应该进入快照包,但不能被评估。在构建时评估它们将失败,因为 JavaScript 类正在扩展本机 Android 类,而此类仅在您在实际的 Android 设备/模拟器上运行应用程序时才可用。因此,如果您使用的是旧版本的 **nativescript-dev-webpack** 插件,则应用程序中可能包含以下内容

if (!global["__snapshot"]) {
    require("ui/frame");
    require("ui/frame/activity");
}
您不再需要该代码。相反,如果您有自定义应用程序组件,请将其添加到 webpack 配置 文件顶部的应用程序组件数组中
const appComponents = [
// ...
resolve(__dirname, "app/main-activity.android.ts"),
];
默认配置以将组件 包含在公共块中 的方式设置
splitChunks: {
cacheGroups: {
vendor: {
            test: (module, chunks) => {
                const moduleName = module.nameForCondition ? module.nameForCondition() : '';
                return /[\\/]node_modules[\\/]/.test(moduleName) ||
                        appComponents.some(comp => comp === moduleName);
            },
}
}
}

最后,默认配置 使用 **android-app-components-loader**,它将引入组件并将它们包含在您的包中
{
    loader: "nativescript-dev-webpack/android-app-components-loader",
    options: { modules: appComponents }
}

常见错误消息以及如何修复

错误

模块构建失败:错误:最终加载器未返回 Buffer 或字符串

解决方案

将 TypeScript 更新到 2.7.2。

错误

无效的配置对象。Webpack 已使用与 API 架构不匹配的配置对象初始化。

- 配置具有未知属性“optimization”。这些属性有效
  对象 { amd?, bail?, cache?, context?, dependencies?, devServer?, devtool?, entry, 

解决方案

更新您的 webpack 依赖项。此脚本自动执行此过程:

./node_modules/.bin/update-ns-webpack --deps