สร้าง Angular Library ไปวางไว้บน npm

เคยคิดว่า ถ้าจะสร้าง component สำหรับ Angular แล้วเอาไปให้คนอื่นได้ใช้ผ่าน npm มันต้องทำยังไง แต่สุดท้ายก็ลืมหาคำตอบ จนมาเกิดคำถามนี้อีกครั้งตอนมา outing ของ odd.Works ที่ตั้มถามว่า ถ้าจะทำ component ไปวางไว้บน npm เนี่ย มันต้องทำยังไง เลยเกิดคำถามว่า เออ มันทำไงวะ เคยได้แต่ถาม ไม่เคยหาคำตอบ งั้น มาลองดิ๊

จริง ๆ แล้ววิธีนี้มันใช้ได้กับทุก ๆ lib ไม่จำเป็นต้องเป็นแค่ component จะเป็น directive หรือ service ก็ได้ แต่อยากลองกับ component เท่านั้นแหละ

เริ่มจาก สร้าง project ก่อน

ตั้งชื่อว่า ng-chonla ละกัน เสร็จแล้วก็ npm init เพื่อสร้าง package.json

mkdir ng-chonla && cd ng-chonla
npm init

กด ๆ Enter ไปจนจบ ก็จะได้ไฟล์ package.json ออกมา

ตัวอย่างนี้ใช้ typescript ด้วยเหตุผลว่า

  • มันเป็น default สำหรับ angular
  • ขี้เกียจเขียนเป็น javascript
  • คนเขียน angular ส่วนใหญ่น่าจะใช้ typescript ได้อยู่แล้ว
  • ตัวอย่างที่ไปเอาไอเดียมาเขาก็ใช้ typescript (จริง ๆ น่าจะเป็นเหตุผลนี้เหตุผลเดียวสินะ)

ถัดมา install @angular/core กับ dependency อื่น ๆ

ลงผ่าน npm นี่แหละ ตัว dependency อื่น ๆ ก็ดูตามที่มันบอกว่าหลังจากลง @angular/core แล้ว มันอยากให้เราลงอะไร เวอร์ชั่นไหนอีก ตอนที่ลองนี่มันบอกมันอยากได้ rxjs@^5.5.0 กับ zone.js@0.8.4

npm install --save @angular/core
npm install --save rxjs@^5.5.0
npm install --save zone.js@^0.8.4

เสร็จแล้วเขียน code

ลองสร้าง component ขึ้นมาชื่อ chonla.component.ts จะได้ไฟล์ ./chonla.component.ts

import { NgModule, Component } from '@angular/core';

@Component({
  selector: 'chonla',
  template: '<h1>chonla</h1>'
})
export class ChonlaComponent {

}

เสร็จปุ๊บ ถ้าใช้ VS Code เป็น editor ที่ลง typescript extension มันจะด่าที่ decorator @Component กับ ChonlaComponent ด้วยเหตุผลเรื่อง decorator สิ่งที่ต้องทำจากนี้คือ เฮ้ย เราจะใช้ decorator ด้วยนะ งั้นไปประกาศ configuration ให้ typescript ให้มันรู้จักกับการใช้ decorator กัน

ประกาศ tsconfig.json

สร้างไฟล์ชื่อ tsconfig.json มีรายละเอียดตามนี้

{
  "compilerOptions": {
    "target": "es5",
    "module": "es2015",
    "sourceMap": true,
    "moduleResolution": "node",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "declaration": true,
    "outDir": "./dist",
    "lib": [
      "es2015",
      "dom"
    ]
  },
  "files": [
    "./ng-chonla.ts"
  ]
}

โดยที่

  • target คือ javascript version ที่จะถูก compile ออกมา
  • module คือ module format ของ javascript ที่จะถูก compile ออกมา ค่าที่ใส่ได้คือ ES2015, UMD, AMD
  • sourcemap คือ บอกว่า typescript มันสร้าง source map file หรือเปล่า
  • moduleResolution คือ บอก typescript ในการ resolve module import ว่าจะ resolve ด้วยวิธีไหน มีให้เลือกเป็น classic กับ node (ดูเพิ่มเติมได้จาก https://www.typescriptlang.org/docs/handbook/module-resolution.html)
  • emitDecoratorMetadata คือ บอก typescript ว่าให้ support emitting metadata บางตัวใน decorator
  • experimentalDecorators คือ บอก typescript ว่าให้ support decorator
  • decoration คือ บอกให้ typescript ให้สร้างไฟล์ .d.ts ด้วย
  • outDir คือ output directory ว่าจะให้ compile ไปไว้ไหน
  • lib คือ lib ที่จะ include เข้าไปด้วย
  • files คือ ไฟล์ที่ให้ typescript มัน compile ด้วย tsc

หลังจากทำเสร็จลองปิดเปิด editor ใหม่ error จะหายไปแล้ว

สร้าง module สำหรับ component

ชื่อว่า ./chonla.module.ts

import { NgModule, } from '@angular/core';
import { ChonlaComponent } from './chonla.component';
@NgModule({
    imports: [],
    exports: [ChonlaComponent],
    declarations: [ChonlaComponent],
    providers: [],
})
export class ChonlaModule { }

สร้างที่รวมของทั้งหมดเข้าด้วยกัน

ถัดมา สร้างไฟล์ ng-chonla.ts (ตามที่ประกาศไว้ที่ tsconfig.json) ​ไว้ที่ root ของโปรเจค ตัว tsc จะมา compile ที่ตัวนี้

export * from './chonla.module';
export * from './chonla.component';

ลอง compile

compile ด้วยคำสั่ง tsc เฉย ๆ นี่แหละ มันควรจะสร้าง directory ชื่อ dist แล้วก็มีของที่ compile แล้วอยู่ในนั้น

เตรียมปล่อยของ

สิ่งที่ต้องใช้มี 2 อย่างคือ .js ซึ่งเป็นของที่เอาไว้ใช้งาน กับ .d.ts ที่เอาไว้ใช้ทำ code auto-complete ตอนเขียน code ทั้งสองอย่างจะถูกสร้างตอนที่ compile ด้วย typescript compiler หรือ tsc

ลองพิมพ์ tsc ดู จะเห็นว่ามี directory ชื่อ ./dist โผล่มา ข้างในมีของที่ compile เสร็จแล้ว ในนั้นจะมี .js, .js.map กับ .d.ts ในเมื่อไฟล์พวกนี้ถูกสร้างตอน compile เราเลยสามารถ ignore มันจาก repo ได้ เพิ่มมันเข้าไปใน .gitignore ได้เลย

# Node generated files
node_modules
npm-debug.log

# OS generated files
Thumbs.db
.DS_Store

# Ignored files
*.js
*.map
*.d.ts

แต่เดี๋ยวก่อน!

ตอนเราสั่ง npm install เนี่ย npm มันไม่ได้ดึงจาก git server แต่มันดึงจาก npm cache server ดังนั้นแปลว่า ตอนเรา publish ของไปไว้ npm server สิ่งที่เกิดขึ้นคือ จะต้อง compile ด้วย tsc เสร็จแล้วเอาของเข้าไปไว้ที่ npm server โดย default มันจะเอาขึ้นไปหมดทุกอย่าง เราไม่อยากเอาอะไรขึ้น npm server ก็ต้องใส่ในไฟล์ .npmignore มีหน้าตาแบบนี้

# Node generated files
node_modules
npm-debug.log

# OS generated files
Thumbs.db
.DS_Store

# Ignored files
*.ts
!*.d.ts

ทำให้ auto หน่อย

ตอนสั่ง npm publish มันจะสั่ง npm prepublish ก่อน ดังนั้น สิ่งที่ต้องทำเพิ่มคือทำ script ลงใน package.json

"scripts": {
  "prepublishOnly": "tsc"
}

IDE integration

ทำให้มัน integrate กับ IDE ที่ใช้โดยใส่ไอ้ข้างล่างนี้ลงใน package.json เหมือนกัน

"typings": "./ng-chonla.d.ts",

ลอง

ลองดูก่อนที่จะ publish จริง ๆ โดยการเข้าไปที่ root ของ project แล้วสั่ง npm link

npm link

สิ่งที่เกิดคือ npm จะสร้าง  node_modules/ng-chonla อยู่ที่ global โดย link มาที่ project ของเรา แทนที่จะต้อง install package นั้นจริง ๆ

เสร็จแล้วลองสร้าง angular project ขึ้นมาอีกอัน แล้วลอง import มาใช้ดู ก่อน import ไม่ต้อง npm install ng-chonla นะ เพราะยังไม่ได้ publish แต่ให้เราใช้ตัวที่เรา link ไว้ ด้วยคำสั่ง npm link ng-chonla

npm link ng-chonla

เสร็จแล้วใน npm_modules ของ local ใน project ที่สร้างใหม่ จะมี ng-chonla โผล่มาให้ import ไปใช้ได้เลย

ลอง เอาไปใส่ใน template ของ app.component.html ดิ๊

<div style="text-align:center">
  <chonla></chonla>
</div>

เสร็จแล้ว import มันมาใส่ใน app.module.ts ด้วย

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { ChonlaComponent } from '../../../ng-chonla/ng-chonla';

@NgModule({
  declarations: [
    AppComponent,
    ChonlaComponent
  ],
  imports: [
    BrowserModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

เสร็จแล้ว ng serve ดู

ng serve

เปิด browser ดิ๊

เฮ้ย ได้แล้ว

ที่เหลือก็ publish ขึ้น npm

กลับไปที่ ng-chonla component ที่ทำตะกี๊ เสร็จแล้ว publish ด้วยคำสั่ง npm publish

npm publish

ถ้ายังไม่เคยมี account npm ไปสร้าง account ให้เรียบร้อยที่ https://www.npmjs.com/signup

เสร็จแล้ว ก็ authorize ด้วยคำสั่ง npm adduser มันจะถาม username, password และ email เสร็จแล้วมันจะ login ให้อัตโนมัติ

npm adduser

login เสร็จแล้ว ก็กลับไป npm publish ใหม่ เป็นอันจบ ก็จะมี package เท่ ๆ ของเราอยู่บน npm แล้วประมาณนี้ https://www.npmjs.com/package/ng-chonla

โค้ดตัวอย่าง

อยู่ที่นี่ https://github.com/chonla/ng-chonla

Leave a Reply