热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

十、用于生产的捆绑包

十、用于生产的捆绑包在将JS应用部署到生产环境中时,捆绑是一种重要的性能实践。通过将资源(主

十、用于生产的捆绑包

在将 JS 应用部署到生产环境中时,捆绑是一种重要的性能实践。通过将资源(主要是 JS 代码、HTML 模板和 CSS 表)合并到一个文件中,我们可以大大减少浏览器为应用服务而进行的 HTTP 调用的数量。

CLI 始终绑定其运行的应用,即使在开发环境中也是如此。这使得将应用部署到服务器非常简单;这只是一个构建它然后复制一堆文件的问题。

但随之而来的是版本控制问题。在部署新版本的应用时,如果捆绑包保持相同的名称,则缓存的捆绑包可能无法刷新,从而导致用户运行过时版本的应用。我们如何处理这个问题?

在本章中,我们将了解如何定制联系人管理应用的绑定。我们还将了解如何利用 CLI 的修订功能对捆绑包进行版本化,以便尽可能从 HTTP 缓存中获益。最后,为了便于部署,我们将向项目中添加一个新的构建任务。

配置捆绑包

默认情况下,使用 CLI 创建的项目包含两个捆绑包:第一个名为vendor-bundle.js,包含应用使用的所有外部库,第二个名为app-bundle.js,包含应用本身。

捆绑包在aurelia_project/aurelia.json文件中的 build 部分下进行配置。以下是它在典型应用中的外观:

"bundles": [
{
"name": "app-bundle.js",
"source": [
"[**/*.js]",
"**/*.{css,html}"
]
},
{
"name": "vendor-bundle.js",
"prepend": [
"node_modules/bluebird/js/browser/bluebird.core.js",
"scripts/require.js"
],
"dependencies": [
"aurelia-binding",
"aurelia-bootstrapper",
"aurelia-dependency-injection",
"aurelia-framework",
//Omitted snippet...
]
}
]

每个捆绑包都有一个唯一的名称,并且必须定义其内容,这些内容可以来源于应用和外部依赖项。通常,app-bundle包含来自应用源的所有 JS、HTML 和 CSS,vendor-bundle包含外部依赖项。

这通常是中小型应用的最佳配置。通常不会经常更改的外部依赖项被分组在自己的包中,因此用户不必在每次发布应用的新版本时下载这些依赖项。在大多数情况下,他们只需下载新的app-bundle

将应用合并到单个捆绑包中

但是,如果出于某种原因,您希望应用能够包含在单个捆绑包中,包括应用本身及其依赖项,那么这样做相当容易。您只需定义一个包,其中包含应用源和外部依赖项:

下一节中的片段摘自该书资产的chapter-10/samples/app-single-bundle样本。

aurelia_project/aurelia.json

"bundles": [
{
"name": "app-bundle.js",
"prepend": [
"node_modules/bluebird/js/browser/bluebird.core.js",
"scripts/require.js"
],
"source": [
"[**/*.js]",
"**/*.{css,html}"
],
"dependencies": [
"aurelia-binding",
"aurelia-bootstrapper",
//Omitted snippet...
]
}
]

由于 Aurelia 应用的入口点是aurelia-bootstrapper库,入口点包必须是包含bootstrapper的那个。默认情况下,这是vendor-bundle。如果您在此处更改入口点包,它将成为app-bundle;你需要改变一些事情。

首先,仍然在aurelia_project/aurelia.jsonbuild下,必须为新的入口点捆绑更改加载程序的configTarget属性:

aurelia_project/aurelia.json

"loader": {
"type": "require",
"configTarget": "app-bundle.js",
// Omitted snippet...
},

此外,index.html的主script标记也必须引用新的入口点包:

index.html






如果此时运行应用,您将看到生成了单个捆绑包,并且浏览器在启动应用时仅加载此捆绑包。

将应用拆分为多个捆绑包

在某些场景中,将整个应用源放在一个app-bundle中是次优的。我们可以很容易地想象一个应用是建立在高度隔离的用户故事之上的。用户仅使用此应用的特定部分,具体取决于其角色。

这样的应用可以分成多个较小的捆绑包,每个捆绑包对应一个与角色相关的部分。这样,用户就不会为他们从未使用过的应用部分下载捆绑包。

以下章节中的片段摘自该书资产的chapter-10/samples/ app-with-home sample

让我们通过将应用的contacts功能移动到它自己的包中来尝试一下。为此,我们首先需要将contacts目录中的所有内容从app-bundle中排除:

aurelia_project/aurelia.json

{
"name": "app-bundle.js",
"source": {
"include": [
"[**/*.js]",
"**/*.{css,html}"
],
"exclude": [
"**/contacts/**/*"
]
}
}

source属性支持一个全局模式数组,或者支持一个具有include和可选exclude属性的对象,这两个属性都应该包含一个全局模式数组。

在这里,我们只需将前面的source值向下移动到include属性,并添加一个与contacts目录中的所有内容匹配的exclude属性。

接下来,我们需要定义新的捆绑包:

aurelia_project/aurelia.json

{
"name": "app-bundle.js",
//Omitted snippet...
},
{
"name": "contacts-bundle.js",
"source": [
"[**/contacts/**/*.js]",
"**/contacts/**/*.{css,html}"
]
},

这个名为contacts-bundle.js的新包将包括contacts目录中的所有 JS、HTML 和 CSS 文件。

如果此时运行应用,首先应该看到scripts目录现在包含三个捆绑包:app-bundle.jscontacts-bundle.jsvendor-bundle.js。如果您在浏览器中打开应用并检查调试控制台,您应该看到在加载应用时,浏览器首先加载vendor-bundle,然后加载app-bundle,最后加载contacts-bundle

当主configure功能在应用启动过程中加载contacts功能时,加载contact-bundle。这是 Aurelia 特性的限制之一:很难将特性分离到一个不同的包中。事实上,功能的index文件及其所有依赖项都应该捆绑在app-bundle中。单独绑定它是没有用的,因为无论如何启动时都会加载另一个绑定。但是,功能中的所有其他内容都可以单独捆绑。

在我们的应用中,即使您进行了此更改,contacts-bundle仍会在应用启动时加载,因为app组件会自动将用户重定向到联系人默认路径,即联系人列表。

如果您在应用中添加一个主组件作为默认路由,并确保该主组件包含在app-bundle中,则您应该看到只有在导航到contacts-bundle时才会加载该contacts-bundle

版本控制包

默认情况下,捆绑包是使用静态名称生成的。这意味着已经缓存了捆绑包副本的浏览器无法知道其副本是否是新的。如果发布了应用的新版本,该怎么办?

解决此问题的一个(糟糕的)解决方案是将缓存持续时间设置为非常短的时间跨度,这会迫使所有用户频繁下载所有捆绑包,或者接受某些用户可能运行过时版本的应用这一事实,这意味着相应地管理与后端、web 服务等的兼容性。这似乎是一个伟大的噩梦食谱。

更好的解决方案是在每个包的名称中添加某种修订号,并将index.html离开的缓存时间设置为非常短的时间跨度,甚至完全禁用其缓存。由于index.html与捆绑包相比非常小,这是一个有趣的权衡,因为每次给定用户访问应用时,他都会下载index.html的新副本,而该副本又会引用捆绑包的最新版本。这意味着捆绑包可以永久缓存,因为给定捆绑包名称的内容永远不会更改。用户不会多次下载给定版本的捆绑包。

Aurelia CLI 通过向文件名添加后缀来支持捆绑包版本控制。此后缀是根据文件内容计算的哈希。默认情况下,版本控制处于禁用状态。要启用它,请打开aurelia_project/aurelia.json文件,并在build部分下设置optionsrev属性:

aurelia_project/aurelia.json

"options": {
"minify": "stage & prod",
"sourcemaps": "dev & stage",
"rev": "stage & prod"
},

修订机制是基于每个环境启用的。通常,它将在暂存和生产中启用。但是,它不应该在开发环境中使用,因为当watch开关与au运行一起使用时,它不能很好地使用浏览器重新加载和包重建机制。此外,由于大多数开发人员在禁用缓存的浏览器中进行系统测试,因此它没有什么价值。

您还必须始终确保在aurelia_project/aurelia.jsonbuild下,targets中的第一个条目的index属性设置为index.html

aurelia_project/aurelia.json

"targets": [
{
"id": "web",
"displayName": "Web",
"output": "scripts",
"index": "index.html"
}
],

这让 bundler 知道加载应用的 HTML 文件的名称,因此它可以更新加载入口点 bundle 的script标记。

现在,您可以通过在项目目录中打开控制台并运行以下命令来测试这一点:

> au build --env stage

命令完成后,您应该在scripts目录中看到捆绑包的名称中包含一个哈希。您应该看到类似于app-bundle-ea03d27d90.jsvendor-bundle-efd8bd9cd8.js的内容,可能有不同的散列。

此外,在index.html中,主体内的script标记的src属性现在应该引用文件名中包含哈希的vendor-bundle文件。

部署应用

此时,部署我们的应用相当容易。我们需要将以下文件复制到托管它的服务器:


  • index.html

  • favicon.ico

  • locales/

  • styles/

  • scripts/

  • node_modules/bootstrap/

  • node_modules/font-awesome/

现在,大多数项目使用某种软件工厂来构建和部署应用。当然,我们可以轻松地将该文件列表放在工厂的构建任务中。但是,这意味着每次向该列表添加文件或目录时,我们都需要更改构建任务。

在处理 Aurelia 项目时,我喜欢做的一件事是在aurelia_project/aurelia.json文件中创建一个新的deploy部分,我将其设置为与要包含在部署包中的文件相匹配的全局模式列表:

aurelia_project/aurelia.json

{
//Omitted snippet...
"build": {
//Omitted snippet...
},
"deploy": {
"sources": [
"index.html",
"favicon.ico",
"locales/**/*",
"scripts/*-bundle*.{js,map}",
"node_modules/bootstrap/dist/**/*",
"node_modules/font-awesome/{css,fonts}/**/*"
]
}
}

除此之外,我还通常在项目中创建一个deploy任务。此任务只是构建应用,然后将要部署的文件复制到目标目录,该目录作为参数传递给任务。

让我们首先创建任务定义:

aurelia_project/tasks/deploy.json

{
"name": "deploy",
"description": "Builds, processes and deploy all application assets.",
"flags": [
{
"name": "out",
"description": "Sets the output directory (required)",
"type": "string"
},
{
"name": "env",
"description": "Sets the build environment (uses debug by default).",
"type": "string"
}
]
}

接下来,我们创建一个copy任务,该任务将被deploy任务使用:

aurelia_project/tasks/copy.js

import gulp from 'gulp';
import {CLIOptions} from 'aurelia-cli';
import project from '../aurelia.json';
export default function copy() {
const output = CLIOptions.getFlagValue('out', 'o');
if (!output) {
throw new Error('--out argument is required');
}
return gulp.src(project.deploy.sources, { base: './' })
.pipe(gulp.dest(output));
}

此任务首先检索作为out参数传递的目标目录,如果省略,则会失败,然后使用aurelia_project/aurelia.json中新deploy部分的 glob 模式列表,并将每个匹配文件复制到提供的目标目录。

最后,我们可以创建部署任务本身:

aurelia_project/tasks/deploy.js

import gulp from 'gulp';
import build from './build';
import copy from './copy';
export default gulp.series(
build,
copy
);

此任务只是按顺序执行buildcopy。我们甚至可以在buildcopy之间运行单元测试任务。

gulp任务大大简化了软件工厂中的构建任务。典型的软件工厂构建过程将首先从版本控制中检出代码,然后运行以下命令:

> npm install
> au deploy --env $(env) --out $(build-artifacts)

最后,它会将$(构建工件)下的所有内容复制到 web 服务器。

在此场景中,$(env)$(build-artifacts)是某种环境或系统变量。第一个包含完成构建的环境,例如stageprod,而第二个包含一些临时文件夹,要从中复制要部署到 web 服务器的工件。例如,它可能只是工作目录中的一个dist文件夹。

这个解决方案的优点之一是,与构建和部署应用相关的大多数细节现在都在项目本身中。软件工厂不依赖于来自应用源的文件结构和文件名,而只依赖于gulp任务。

总结

由于 CLI 一直在捆绑模式下运行应用,部署 Aurelia 应用一开始似乎非常简单。然后,您开始考虑 HTTP 缓存过期问题,事情变得有点复杂。

谢天谢地,CLI 已经提供了解决这些问题的工具。这与一些良好的实践一起,使得为真实世界准备应用变得非常简单。


推荐阅读
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • Webpack5内置处理图片资源的配置方法
    本文介绍了在Webpack5中处理图片资源的配置方法。在Webpack4中,我们需要使用file-loader和url-loader来处理图片资源,但是在Webpack5中,这两个Loader的功能已经被内置到Webpack中,我们只需要简单配置即可实现图片资源的处理。本文还介绍了一些常用的配置方法,如匹配不同类型的图片文件、设置输出路径等。通过本文的学习,读者可以快速掌握Webpack5处理图片资源的方法。 ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • Voicewo在线语音识别转换jQuery插件的特点和示例
    本文介绍了一款名为Voicewo的在线语音识别转换jQuery插件,该插件具有快速、架构、风格、扩展和兼容等特点,适合在互联网应用中使用。同时还提供了一个快速示例供开发人员参考。 ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • Nginx使用AWStats日志分析的步骤及注意事项
    本文介绍了在Centos7操作系统上使用Nginx和AWStats进行日志分析的步骤和注意事项。通过AWStats可以统计网站的访问量、IP地址、操作系统、浏览器等信息,并提供精确到每月、每日、每小时的数据。在部署AWStats之前需要确认服务器上已经安装了Perl环境,并进行DNS解析。 ... [详细]
  • 基于layUI的图片上传前预览功能的2种实现方式
    本文介绍了基于layUI的图片上传前预览功能的两种实现方式:一种是使用blob+FileReader,另一种是使用layUI自带的参数。通过选择文件后点击文件名,在页面中间弹窗内预览图片。其中,layUI自带的参数实现了图片预览功能。该功能依赖于layUI的上传模块,并使用了blob和FileReader来读取本地文件并获取图像的base64编码。点击文件名时会执行See()函数。摘要长度为169字。 ... [详细]
  • 本文介绍了使用AJAX的POST请求实现数据修改功能的方法。通过ajax-post技术,可以实现在输入某个id后,通过ajax技术调用post.jsp修改具有该id记录的姓名的值。文章还提到了AJAX的概念和作用,以及使用async参数和open()方法的注意事项。同时强调了不推荐使用async=false的情况,并解释了JavaScript等待服务器响应的机制。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • Mac OS 升级到11.2.2 Eclipse打不开了,报错Failed to create the Java Virtual Machine
    本文介绍了在Mac OS升级到11.2.2版本后,使用Eclipse打开时出现报错Failed to create the Java Virtual Machine的问题,并提供了解决方法。 ... [详细]
  • 本文介绍了在SpringBoot中集成thymeleaf前端模版的配置步骤,包括在application.properties配置文件中添加thymeleaf的配置信息,引入thymeleaf的jar包,以及创建PageController并添加index方法。 ... [详细]
  • 知识图谱——机器大脑中的知识库
    本文介绍了知识图谱在机器大脑中的应用,以及搜索引擎在知识图谱方面的发展。以谷歌知识图谱为例,说明了知识图谱的智能化特点。通过搜索引擎用户可以获取更加智能化的答案,如搜索关键词"Marie Curie",会得到居里夫人的详细信息以及与之相关的历史人物。知识图谱的出现引起了搜索引擎行业的变革,不仅美国的微软必应,中国的百度、搜狗等搜索引擎公司也纷纷推出了自己的知识图谱。 ... [详细]
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
author-avatar
兄弟465748208_279
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有