Aurelia 2 從零開始之建構專案

這篇紀錄了身為 Node.js 門外漢的我從零開始建構一個 Aurelia 2 專案的實況。

Aurelia 是一套前端框架,類似於 React、Augular、Vue,與三天王最大的差異就是知名度低、台灣沒人用、沒有豐富的元件庫可以用,大多只能自幹,幸好它還是處於有人維護的狀況。

Aurelia 的特色是不使用 virtual DOM,而使用 shadow DOM,以及同樣是 HTML 標準的 custom element 來實現網頁元件化。

Aurelia 2 則是 Aurelia 的第二代,目前還在開發階段,只適合拿來當玩具,還不適合拿來做產品。本文所用的版本是 Aurelia 2 v0.6.0,主要的參考文件來自 https://docs.aurelia.io/。

附註一下 Aurelia 1 與 Aurelia 2 的文件庫各自的位址:

  • Aurelia 1: https://aurelia.io/docs/
  • Aurelia 2: https://docs.aurelia.io/

我用的環境是 Elementary OS 5.1.2 Hera,相當於 Ubuntu 18.04.3 LTS,下文也都會以這樣的環境為基礎。

前置作業

這部份講 Git、Node.js、Visual Studio Code 等,都有的可跳過。

安裝 Git

sudo apt install git

安裝 Node.js 與 NPM

一般來說只要安裝 Node.js 也會一併安裝 NPM。Node.js 安裝的管道包括下列幾種:

  • OS 預帶的軟體庫內的版本
  • Node.js 提供的 deb 軟體庫
  • Node.js 提供的 snap 軟體庫
  • 透過 nvm 安裝的 Node.js

OS 預帶的軟體庫過舊直接排除,其它三個都是可行的選項,對初學者我來說是用 Node.js 提供的 deb 軟體庫來做安裝,具體的操作細節可以參考這份文件:NodeSource Node.js Binary Distributions

在這裡裝的版本是 Node.js 12 LTS。

# Using Ubuntu
curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash -
sudo apt-get install -y nodejs

安裝 Visual Studio Code

去 https://code.visualstudio.com/ 抓 deb 包回來裝。

最前面提到 Aurelia 的特色,社群小,加上 Aurelia 2 又是在早期開發階段,因此 VSC 是沒有相關的延伸模組可以使用的,請自幹吧。雖然如此,Git、JavaScript、TypeScript、HTML、CSS 相關的延伸模組還是很豐富,可以善加利用。

建構 Aurelia 2 專案

下面這行起手式指令會以問答的方式逐步帶領我們建立專案目錄與檔案:

npx makes aurelia

這行指令會下載 Aurelia 套件與相關的依賴套件,然後執行 Aurelia 套件內預先定義好的問答程序:

Please name this new project: › my-app

專案的名稱,同時也會是專案目錄的名稱,Aurelia 會以這個名稱建立一個目錄並把雛型目錄與檔案(scaffold,有人翻譯成不易理解的腳手架)置入。

Would you like to use the default setup or customize your choices? › Custom Aurelia 2 App
❯ Default ESNext Aurelia 2 App
  A basic Aurelia 2 App with Babel and Webpack
❯ Default TypeScript Aurelia 2 App
  A basic Aurelia 2 App with TypeScript and Webpack
❯ Custom Aurelia 2 App
  Select bundler, transpiler, and more.

因為本人不想用 Webpack,所以選自訂。

Which bundler would you like to use? › Dumber
❯ Webpack
  A powerful and popular bundler for modern JavaScript apps.
❯ Dumber
  A dumb JavasScript bundler, dumber than you and me. The successor of Aurelia CLI built-in bundler.

因為本人偏好傻瓜方案所以選 Dumber

What transpiler would you like to use? › Babel
❯ Babel
  An open source, standards-compliant ES2015 and ESNext transpiler.
❯ TypeScript
  An open source, ESNext superset that adds optional strong typing.

Babel,因為本人不會 TypeScript。

Do you want to use Shadow DOM? › Yes
❯ No
❯ Yes
  Shadow DOM in open mode, shadowRoot is accessible through DOM API.

不太確定如果不用 shadow DOM 的話 Aurelia 會怎麼處理組件邏輯、樣式需要被隔離的問題,用 virtual DOM 還是用改命名空間的方式呢?不太確定先選 Yes,用 Shadow DOM。

更新

不用 shadow DOM 不會用 virtual DOM,會用 CSS 命名的方式來隔離組件樣式。

What CSS preprocessor to use? › Plain CSS
❯ Plain CSS
❯ Less (.less)
❯ Sass (.scss)

Plain CSS 用好用滿。

What unit testing framework to use? › None
❯ Jasmine
  Runs in browser, a behavior-driven testing framework.
❯ Mocha + Chai
  Runs in browser, a feature-rich JavaScript test framework for node and browsers.
❯ Tape
  Runs in browser, tap-producing test harness for node and browsers.
❯ None
  Skip testing. My code is always perfect 🙂

None,我的碼就是完美無缺!

Do you want to setup e2e test? › No
❯ No
❯ Yes (Cypress)
  Cypress offers fast, easy and reliable testing for anything that runs in a browser.

No,說不測就不測。

Do you want to install npm dependencies now? › Yes, use npm
❯ No
❯ Yes, use npm

順便把有用到的套件都裝起來。

問答程序結束後,應該會多出一個 my-app 資料夾,裡面大概會長這樣:

.
├── babel.config.json
├── dist
├── favicon.ico
├── gulpfile.js
├── _index.html
├── index.html
├── node_modules
├── package.json
├── package-lock.json
├── README.md
├── src
│   ├── main.js
│   ├── my-app.css
│   ├── my-app.html
│   ├── my-app.js
└── test
  • src:放置我們的程式碼的資料夾
  • dist:編譯過後的輸出目標資料夾

執行專案

雖然目前是空的專案,但前面的設定程序有幫我們做了一個簡單的 Hello World! 頁面,把專案跑起來看看:

npm start

應該會輸出這些訊息:

> my-app@0.1.0 start /home/leon/Git/my-app
> gulp
[04:46:05] Using gulpfile ~/Git/my-app/gulpfile.js
[04:46:05] Starting 'default'...
[04:46:05] Starting 'clean'...
[04:46:05] Finished 'clean' after 29 ms
[04:46:05] Starting 'build'...
[dumber] local dependency say-hello (requiredBy my-app.js, my-app.html.js) is missing
[dumber] 0.7.0-dev.202002082141 @aurelia/kernel
[dumber] 0.7.0-dev.202002082141 @aurelia/runtime
[dumber] 0.7.0-dev.202002082141 aurelia
[dumber] 0.7.0-dev.202002082141 @aurelia/debug
[dumber] 0.7.0-dev.202002082141 @aurelia/fetch-client
[dumber] 0.7.0-dev.202002082141 @aurelia/jit
[dumber] 0.7.0-dev.202002082141 @aurelia/jit-html-browser
[dumber] 0.7.0-dev.202002082141 @aurelia/router
[dumber] 0.7.0-dev.202002082141 @aurelia/runtime-html
[dumber] 0.7.0-dev.202002082141 @aurelia/jit-html
[dumber] 0.7.0-dev.202002082141 @aurelia/runtime-html-browser
[dumber] 0.11.10    process
[dumber] 1.11.1     tslib
Update index.html with entry-bundle.js
[04:46:08] Write app-bundle.js
[04:46:08] Write entry-bundle.js
[04:46:10] Finished 'build' after 4.91 s
[04:46:10] Starting 'startServer'...
[Browsersync] Access URLs:
 ----------------------------
 Local: http://localhost:9000
 ----------------------------
    UI: http://localhost:3001
 ----------------------------
[Browsersync] Serving files from: .
Application Available At: http://localhost:9000
BrowserSync Available At: http://localhost:3001
[04:46:10] Finished 'startServer' after 124 ms
[04:46:10] Starting 'watch'...

由 Gulp 調度工作,Dumber 負責整合打包用到的套件,啟動伺服器並打開瀏覽器開啟 http://localhost:9000/,應該會在瀏覽器看到醜醜的 Hello World!。

Hello World!

再用 Inspector 看一下,確實是有 custom element 與 shadow DOM。

Inspector

編譯專案

在 package.json 與 gulpfile.js 裡面同時也幫我們準備好了編譯的指令。

package.json 裡面的 build 會先執行 gulp clean 再執行 gulp build:

"scripts": {
  "lint:html": "htmlhint -c .htmlhintrc src",
  "lint": "npm run lint:js && npm run lint:html",
  "start": "gulp",
  "build": "gulp clean && cross-env NODE_ENV=production gulp build",
  "clear-cache": "gulp clear-cache",
  "lint:js": "eslint src"
}

gulpfile.js 的 clean() 很簡單,只是把 dist 資料夾砍掉而已;build() 則負責把所有用到的 js / html / css 檔案都包起來丟給 Dumber 去處理依賴包:

function build() {
  // Merge all js/css/html file streams to feed dumber.
  // dumber knows nothing about .ts/.less/.scss/.md files,
  // gulp-* plugins transpiled them into js/css/html before
  // sending to dumber.
  return merge2(
    gulp.src('src/**/*.json'),
    buildJs('src/**/*.js'),
    buildHtml('src/**/*.html'),
    buildCss('src/**/*.css')
  )
  // Note we did extra call `dr()` here, this is designed to cater watch mode.
  // dumber here consumes (swallows) all incoming Vinyl files,
  // then generates new Vinyl files for all output bundle files.
  .pipe(dr())
  // Terser fast minify mode
  // https://github.com/terser-js/terser#terser-fast-minify-mode
  // It's a good balance on size and speed to turn off compress.
  .pipe(gulpif(isProduction, terser({compress: false})))
  .pipe(gulp.dest(dist, {sourcemaps: isProduction ? false : '.'}));
}
function clean() {
  return del(dist);
}

了解了背後的流程後就來真正的跑一次:

npm run-script build

跑完後除了 dist 資料夾內有被打包過的 app-bundle.xxx.js 與 entry-bundle.xxx.js 之外,外面的 index.html 也有被改過,內容的 script 指向 dist 內新的 JavaScript 檔案。


至此門外漢的 Aurelia 專案建構全紀錄結束,下一篇來寫簡單的元件。