angular js 使用pdf.js_如何使用 Angular Static Site Generator: Scully.io

73bc25d7703a344d8de4d568e6ad302a.png

如果说 React 有 Gatsby 和 Next,Vue.js 有 Gridsome 和 Nuxt,那么 Angular 未来可能就是刚刚发展起来的 Scully 。Scully 在 2019 年末左右公开,它是第一个尝试把 JAMstack 带进 Angular 的产品,也是第一个专门的 Angular 静态站点生成器。那么 Scully 如何从 Gatsby & Next,Gridsome & Nuxt 获取到的灵感呢?

原来, Scully 创始者 Aaron Frost 认为,Gatsby 、Next 、Gridsome 和 Nuxt 他们都具有一个同样的缺陷。 就拿 Gatsby 来说,Gatsby 作为 SSG 却和 React 本身的框架是两种不同的 "paradigms"。这样就导致了,如果想让项目使用 Gatsby 这个静态站点生成器,那么你必须得从项目最开始就配置使用它。但是在这方面 Scully 却是不同的:

You write your Angular app, then use Scully to pre-render it and generate static HTML. It doesn’t get in the way of your regular Angular development.
在你已有的 Angular 项目的基础上,用 Scully 来预渲染整个项目,生成一系列静态的 HTML 文件。 Scully 不会对正常的 Angular 应用开发产生任何影响。

Scully 最美妙的地方在于,即使是一个已经写好的项目,你也可以临时配置 Scully 来为项目生成 static sites

Scully work with your Angular app, without any change in your code.
Scully 和 Angular 应用兼容,不会对应用的源代码带来任何改变。

如果换句话评价一下 Scully 的优势,"Scully is less opinionated" 。由此可见,Scully 正是 Angular Community 当下最需要的。

Aaron Frost 也提到了他对 Scully 的野心。他说,在未来 Scully 对 Angular 的支持发展稳定之后,研发团队计划继续拓展到 React 市场。由于 Scully 的这个优势,它将是 Gatsby & Next 的有力竞争对手。

如果你对于 JAMstack 和 SSG 这些概念不太熟悉,那么我们来简单讲讲为什么要使用 SSG 。

静态站点生成器带来的好处。

  • 更好的性能。网站内容都是通过内容分发网络(CDN)获取,不需要通过相对很慢的服务端访问。
  • 更高的安全性。不使用服务端访问来获取网站内容也意味着更少的安全隐患。
  • 更效率的开发周期。不需要建立和维护很复杂的项目架构或者 "infrastructure" 。

当然,Scully 只是刚刚发展起来,它的官方文档也还有很多可以提升的空间。但是这并不是说不值得去阅读他的官方文档。如果你想更好的了解 Scully ,请前往 Scully.io 。

接下来的部分,我会总结一下 Scully 的使用方法。

Scully 的使用。

简单的来说,项目组使用 Scully 会有以下流程:

  1. 正常的创建并开发一个 Angular 应用,包括 Services 和 Components (以及 Lazy-loading Modules )。
  2. 给 Angular 应用添加 Scully 。一个名叫scully.{projectName}.config.js的文件会在项目根目录下默认生成。
  3. 按实际项目的情况,定义并注册自己需要的 plugins (包括 Router Plugins,Render Plugins 和 File Handler Plugins ,具体的会在文章后面介绍)。
  4. scully.{projectName}.config.js文件中根据 ScullyConfig Interface 配置 Scully。这个步骤涉及到根据路由匹配来配置你上一步里定义并注册的 plugins 。
  5. Build Angular 应用。这会在 /dist/{projectName} 下生成 Angular 应用的 distribution 文件。
  6. Run Scully 。根据需要加上 Scully CLI options 。这会 build Scully 并 pre-render 项目从而在指定的路径下生成静态站点的文件。你也可以加上 serve option 默认创建两个服务器,一个是为了 Angular 应用,另一个是为了 Scully build 。

为了更好的理解,我接下来将使用一个简单的项目 Scully Example 当作例子。具体代码请参考Github scully-example

5e0ee10e61d2cda77c4f3034e9ababee.gif
图 1: Scully Example 项目简单演示

Scully Example 网站使用的新闻信息是从 HackerNews 官方的 Firebase 随机挑选的。为了方便演示 Scully ,所有信息是存储在 new.json 静态文件 (为了简化,只存储了三个 news 的 JSON 信息)。该项目使用了一个名为 news 的 Lazy-loading module 。具体文件结构如下:

89586bef63b3383eb2e2395f261f416f.png
图 2:项目基本项目文件结构

接着,我们就来展示如何把 Scully 加进去吧。

按照提到的第一步,打开你的 terminal ,输入以下 command :

ng add @scullyio/init

这产生如下改变:

  • 将 Scully 加入到 dependencies 中。
  • 更新 src/app/app.module.ts 文件。将 ScullyLibModule 添加到 imports 里。
import { ScullyLibModule } from '@scullyio/ng-lib';

@NgModule({
  declarations: [AppComponent, AboutComponent, HomeComponent],
  imports: [BrowserModule, AppRoutingModule, HttpClientModule, ScullyLibModule],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}
  • 更新 src/polyfills.ts 文件。添加如下代码:
/***************************************************************************************************
* SCULLY IMPORTS
*/
// tslint:disable-next-line: align
import 'zone.js/dist/task-tracking';
  • 更新 package.json 文件。
  • 创建 scully.{projectName}.config.js 文件。对于我们的项目来说,也就是 scully.scully-example.config.js 文件。文件最初包含如下代码:
exports.config = {
    projectRoot: './src',
    projectName: 'scully-example',
    outDir: './dist/static',
    routes: {},
};

outDir 属性是指 Scully build 生成预渲染文件的放置路径,默认就是 ./dist/static

重点就是 routes 属性,它用于配置那些需要额外处理的 routes ,之后定义并注册的 plugins 都是在这里面配置。

如果我们直接 build 我们的应用和 Scully,

ng build
npm run scully

我们会发现:

5fb3f41445ee74df8340df726e95a4ed.png
图 3:Terminal 截图

截图最后一行告诉了我们,我们可能需要为提到的 route 进行额外配置。这时就涉及到了 Scully Plugins 的定义了。

我们还可以去查看 /dist/static/assets/scully-routes.json 文件,我们会发现下列代码:

[{ "route": "/" }, { "route": "/about" }, { "route": "/news" }]

scully-routes.json 文件会为我们列出所有 Scully 已经生成的预渲染的 routesScully 会自动预渲染这些 routes 是因为它们本身就只包含静态的内容。其中 /news 会被预渲染也是因为列表里的 news titles 数据都是来自本地静态 assets/news.json 文件。

由于 Scully 是一个很新的 SSG,目前支持三种 Plugins:

  • Router Plugin 。任何在网页路径里含有 router-paramsroute 都需要在 Router Plugin 里面进行配置。 Router Plugin 的作用就是告诉 Scully 如何根据 router-params 来获得 static 文件需要的将被预渲染数据。
  • Render Plugin 。 所有在 Angular 完成渲染之后的 HTML 文件都将被提供给 Render Plugin 进行再一次的改变。
  • File Handler Plugin 。 在渲染过程中,File Handler Plugin 会被 contentFolder Plugin 调用。 contentFolder Plugin 用于对指定类型的文件进行解析,例如 Markdown 类型文件。

要自定义一个新的 Plugin ,Scully 为我们提供了 registerPlugin()方法。 registerPlugin() 方法需要四个参数:

  • typerenderrouterfileHandler
  • name :plugin 的名字, 用于配置。
  • plugin : plugin 方法本身。
  • validator :一个 validation 方法。

需要注意的是,

任何新的 Scully plugin 都需要在 .js 文件中定义并被 exported 。同时他们也必须在 scully.{projectName}.config.js 文件中通过 require() 而被 required。

Router Plugin

在这个项目中我们需要为 /news/:id 进行 plugin 配置。

Scully 为我们提供了一个 router plugin 的 模版:

function exampleRouterPlugin(route: string, config: any): Promise<HandledRoute[]> {
  // Must return a promise
}

interface HandledRoute {
  route: string;
}

根据这些所有的信息,我们首先创建一个 plugins/newsPlugin.js 文件,在里面定义我们的 Plugin function 和 validator。如下列代码所示,其实 newsPlugin 就是返回 handledRoutes,一串 HandledRoute 类型的数组。

const { registerPlugin, routeSplit } = require("@scullyio/scully");
const { httpGetJson } = require("@scullyio/scully/utils/httpGetJson");

const NewsPlugin = "news";

const newsPlugin = async (route, config) => {
  const list = await httpGetJson(config.url);
  const { createPath } = routeSplit(route);
  const handledRoutes = [];
  for (let item of list) {
    handledRoutes.push({
      route: createPath(item.id),
    });
  }
  return handledRoutes;
};

const newsPluginValidator = async (conf) => [];

registerPlugin("router", "news", newsPlugin, newsPluginValidator);
exports.NewsPlugin = NewsPlugin;

config 参数包含了所有我们需要用来创建 "list of urls" 的配置信息。该例子中,config.url包含的就是我们配置时提供的 url

createPath 方法通过 route 参数生成。该方法基于通过 httpGetJson() 获取到的 JSON 数据作为参数,以及原本的 route,来生成 string 类型的 url 。

至于 validatorvalidator 一般的作用是在执行 plugin function 之前对 config 里面的数据进行一系列的验证,将没有通过验证的数据对应的 error 推进 errors 数组里。最终返回 errors 数组。

由于这个例子我们不需要使用任何的 validation 方法,所以 newsPluginValidator 只需要返回 [] 就好。

接下来我们只需要调用 Scully 提供的 registerPlugin() 方法,同时别忘记 export 我们新定义的 Plugin 。请注意我们只需要 export 一串自定义的字符串 'news' ,而不是 Plugin function 本身。

// ABOVE CODE...

// DO NOT FORGET TO REGISTER THE PLUGIN
registerPlugin("router", "news", newsPlugin, newsPluginValidator);
exports.NewsPlugin = NewsPlugin;

最后一步,我们只需要在 scully.scully-example.config.js 文件里配置 newsPlugin 。这里的 url 就是指能直接 fetch 我们的 JSON 数据的 url 。

const { NewsPlugin } = require("./plugins/newsPlugin");

exports.config = {
  projectRoot: "./src",
  projectName: "scully-example",
  outDir: "./dist/static",
  routes: {
    "/news/:id": {
      type: NewsPlugin,
      url: "http://my-json-server.typicode.com/yangjunhan/demo/news",
    },
  },
};

现在我们重新 build 项目并 run Scully 。由于我们添加了一个 route 的额外配置,我们需要加上 --scanRoutes 的 Command Line Options 。 --scanRoutes options 会完整地检测一遍所有的 routes

ng build
npm run scully -- --scanRoutes

这次之后果然 Scully 查找到了 /news/:id 的配置,并预渲染了所有本地静态 JSON 文件对应 idroutes 下的网页 。

b59ee041d46d8f04c0dcf943812a3a67.png
图 4:Terminal 截图

如果我们再确认一次 /dist/static/assets/scully-routes.json 文件:

[
  { "route": "/" },
  { "route": "/about" },
  { "route": "/news" },
  { "route": "/news/23035019" },
  { "route": "/news/23029396" },
  { "route": "/news/23039355" }
]

/dist/static/路径下创建了名为 news 的文件夹,其中包含了一系列被 Scully 预渲染而生成的 HTML 文件。

0c23f3c92ac248eb4399df11ceba8753.png
图 5:Scully 生成的预渲染文件结构

以上展示了如何配置自定义的 Router Plugin 。

Render Plugin

接下来我们再尝试配置一个简单的 Render Plugin 。

我们首先创建一个新的 .js 文件,/plugins/colorPlugin.jscolorPlugin 的目标是为所有 <p> tag 加上 color: red 的 style 设置。

跟 Router Plugin 一样的, 我们需要再次使用到 registerPlugin() 方法。因此,我们首先还是定义我们的 Render Plugin function 和 validator 。

这是官方提供的自定义 Render Plugin Interface

function exampleContentPlugin(HTML: string, route: HandledRoute): Promise<string> {
  // Must return a promise
}

遵循该 Interface,我们需要保证最终返回值必须是 Promise 。

const { registerPlugin } = require("@scullyio/scully");

const ColorPlugin = "color";

const colorPlugin = async (html, route) => {
  const splitter = "</head>";
  const [begin, end] = html.split(splitter);
  const colorStyle = "<style>p {color:red}</style>";
  return Promise.resolve(`${begin}${colorStyle}${splitter}${end}`);
};

const colorPluginValidator = async () => [];

colorPlugin 方法就是通过在 <head></head> 之间加入字符串 <style>p {color:red}</style>,最终用 Promise.resolve() 把重新拼凑的完整 HTML 字符串包裹起来,从而把字符串转换为 Promise 类型。

同样的,我们还是不考虑 validator 的问题。

定义好了 Plugin function 和 validator,我们接下来只需要按照注册 Router Plugin 相似的步骤:

// ABOVE CODE...

// DON NOT FORGET REGISTER THE PLUGIN
registerPlugin("render", ColorPlugin, colorPlugin, colorPluginValidator);
exports.ColorPlugin = ColorPlugin;

最后,我们在 scully.scully-example.config.js 里 require 并配置我们的 Renderer:

const { NewsPlugin } = require("./plugins/newsPlugin");
const { ColorPlugin } = require("./plugins/colorPlugin");

exports.config = {
  projectRoot: "./src",
  projectName: "scully-example",
  outDir: "./dist/static",
  routes: {
    "/news/:id": {
      type: NewsPlugin,
      url: "http://my-json-server.typicode.com/yangjunhan/demo/news",
      postRenderers: [ColorPlugin],
    },
  },
}; 

注意这里的 postRenderers/news/:id 下的属性,这代表了 ColorPlugin 只会在这些 routes 下被使用。

当然,我们也可以通过 Scully Config Interface 下的 defaultPostRenderers: string[]; 属性来设置全局的默认 Render Plugins 。需要注意的是, defaultPostRenderers 会在有设置 postRenderersroutes 下被覆盖掉。

最后,我们只需要重新 build Scully 并运行 Scully 提供的 Scully static server (默认端口:1668) 。 Scully serve 同时也会启动 Angular distribution server (默认端口:1864) 。两个 servers 的端口都可以在 Scully Config Interface 里重新设置。

npm run scully -- --scanRoutes
npm run scully:serve

d145a097ac256fe6c96d00154322947f.gif
图 6:配置 Router Plugin 和 Render Plugin 之后的项目演示

颜色果然成功变成红色,我们的 ColorPlugin 配置成功!

现在,我们已经大概了解了 Scully 的 Router Plugins 和 Render Plugins。当然, Scully 目前已经为我们提供了一些现成的 Plugins。如果想要了解,欢迎前往他们的官方文档 - List of Plugins。

File Handler Plugin

最后,关于 File Handler Plugin,Scully 同样已经提供了对 asciidocMarkdown 类型文件的 fileHandler Plugins 支持。如果你需要自定义对一些特殊文档类型支持,请参考以下 fileHandler Plugin Interface:

function exampleFileHandlerPlugin(rawContent: string): Promise<string> {
  // Must return a promise
}

一般来说,File Handler Plugin 的目的就是为了"格式化" rawContent ,例如利用正则表达式对 rawContent 进行字符串匹配,并用 Table 的 HTML tags 把匹配的内容包起来。

Scully 的 Blogging

Scully 本身也提供了在 Angular 项目里使用静态 Blog 的支持。Blog 本质上就是 Markdown 类型的文件,而 Scully 则会把这些 .md 文件的内容自动预渲染成静态的 HTML 文件。

让我们继续使用上面的同一个项目作为演示。首先,我们需要为项目添加 Blog 的支持。

ng g @scullyio/init:blog

这行 Command 会自动为项目添加一个名为 blog 的 lazy-loading module 。同时,它会在项目路径下创建一个blog 文件夹,文件夹里面自动含有一个默认的 Markdown 文件。

6baafcc768f3c04375fb175c3fc6d9bd.png
图 7:添加 Scully Blog 支持而生成的 blog 文件夹

下面就是 blog .md 文件的默认格式:

---
title: 2020-05-06-blog
description: blog description
published: false
---

# 2020-05-06-blog 

接下来我们生成一个新的 blog post ,我们叫它 "Demo Blog"。

ng g @scullyio/init:post --name="Demo Blog"

于是,一个新的名为 demo-blog.md 的文件会在 /blog/ 文件夹里生成。

8e62c9401300a5e5d10fd7c5dd71cca2.png
图 8:Blog Posts 指令生成的 Markdown 文件

我们将它的内容更改成以下:

---
title: Demo Blog
description: blog description
published: true
---

# Demo Blog

Scully is the best option for moving a blog to Angular!
 

注意,blog 里面的 published 必须设为 true 内容才能被访问。

默认 Scully Blog 支持的设置里,Blog 是通过 /blog/:slug 路径被访问的。这里的 slug 默认是指 Blog 的 title,在我们的演示项目里,也就是 /blog/demo-blog

接下来,我们简单的在 /src/app.component.html 文件里添加一个导航按钮:

<li><a href="blog/demo-blog">Demo Blog</a></li> 

最后,我们只需要重新 build 我们的项目以及 Run Scully:

ng build && npm run scully -- --scanRoutes

再启动我们的服务器:

npm run scully:serve

Blog 的内容成功的加载出来啦!

ce6ce9d84387dc44f5e30824f79d182b.gif
图 9:Scully static server 里 Blog 的内容加载演示

需要注意的是,Blog 的内容只能在 Scully static server 里成功显示出来。如果我们尝试在一般的 Angular distribution server 里显示 Blog 的话。。。

4d13463a6039fb1036fb63c9e3a3c326.png
图 10:Angular distribution server 里 Blog 内容无法加载

结尾

据我所知,每个 SSG 的核心其实就是看他们为使用者提供的 Plugins。 目前来说,由于 Scully 才刚刚起步,现成的 Plugins 确实还太少,但是相信不久的将来,Scully 会拥有数量可观的现成 Plugins 以及用户群。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值