Rollup构建

Rollup打包后的体积较小,可以做一些小框架、小项目

安装依赖

  1. 全局安装rollup

    1
    npm install rollup -g
  2. 安装TypeScript

    1
    npm install typescript -D
  3. 安装TypeScript 转换器

    1
    npm install rollup-plugin-typescript2 -D
  4. 安装代码压缩插件

    1
    npm install rollup-plugin-terser -D
  5. 安装rollupweb服务

    1
    npm install rollup-plugin-serve -D
  6. 安装热更新

    1
    npm install rollup-plugin-livereload -D
  7. 引入外部依赖

    1
    npm install rollup-plugin-node-resolve -D
  8. 安装配置环境变量用来区分本地和生产

    1
    npm install cross-env -D
  9. 替换环境变量给浏览器使用

    1
    npm install rollup-plugin-replace -D

配置文件

rollup.config.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import ts from "rollup-plugin-typescript2"    // 让rollup能够识别TS
import serve from "rollup-plugin-serve" // 启动前端服务
import livereload from "rollup-plugin-livereload" // 服务热更新
import { terser } from "rollup-plugin-terser" // 代码压缩
import replace from "rollup-plugin-replace" // 向浏览器环境注入变量
// ES6模块下获取绝对路径的方式
import path from "path"
import { fileURLToPath } from "url"
const metaUrl = fileURLToPath(import.meta.url)
const dirName = path.dirname(metaUrl)

// 检测目前是否处于开发环境
const isDev = ()=> {
return process.env.NODE_ENV === "development"
}

export default {
input: "./src/index.ts", // 入口
output: { // 出口
file: path.resolve(dirName,"./lib/index.js"),
format: "umd",
sourcemap: true
},


plugins:[
ts(),
isDev() && livereload(), // 如果是开发环境,打开服务,否则不打开
terser(),
replace({
"process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV)
}),
isDev() && serve({
open: true, // 自动打开网页
port: 11451, // 服务端口
openPage: "/public/index.html" // 打开的文件
})
]
}

package.json:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
{
"name": "test2",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
// --bundleConfigAsCjs:强制将配置转译为CommonJS
// 即使配置本身是作为ES模块编写的,也可以使用CommonJS常用的方法
"dev": "cross-env NODE_ENV=development rollup -c -w",
"build":"cross-env NODE_ENV=produaction rollup -c"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"cross-env": "^7.0.3",
"rollup-plugin-livereload": "^2.0.5",
"rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-replace": "^2.2.0",
"rollup-plugin-serve": "^1.1.0",
"rollup-plugin-terser": "^7.0.2",
"rollup-plugin-typescript2": "^0.31.1",
"typescript": "^4.5.5"
}
}

设置tsconfig.json:

1
"sourceMap": true

Webpack构建

安装依赖

  1. 安装webpack

    1
    npm install webpack -D
  2. webpack4以上需要

    1
    npm install  webpack-cli -D
  3. 编译TS

    1
    npm install ts-loader -D
  4. TS环境

    1
    npm install typescript -D
  5. 热更新服务

    1
    npm install  webpack-dev-server -D
  6. HTML模板

    1
    npm install html-webpack-plugin -D

配置文件

webpack.config.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const path = require("path")
const HtmlWebpackPlugin = require("html-webpack-plugin")

module.exports = {
entry: "./src/index.ts", // 入口
output: { // 出口
path: path.resolve(__dirname,"./dist"),
filename: "index.js"
},
module: {
rules: [ // 使用loader
{
test: /\.ts$/, // 正则匹配文件类型
use:"ts-loader"
}
]
},
devServer: {
port: 11451, // 服务端口号
proxy: { // 跨域

}
},
resolve: {
extensions: [".js",".ts"] // import时无需再写后缀
},
plugins: [ // 插件
new HtmlWebpackPlugin({ // 指定html模板
template: "./public/index.html"
})
],
mode: "development"
}

esbuild+SWC构建

esbuild

esbuild使用go语言编写,多线程执行,性能是js的好几十倍,所以很快

esbuild的优点:

  • 无需缓存即可实现基础打包
  • 支持ES6跟CommonJS 模块
  • 支持ES6 Tree Shaking
  • 体积小
  • 插件化
  • 其他
  • 内置支持编译jsx

Vite的开发模式基于esbuild

SWC

SWC是用Rust写的,所实现的功能跟babel一样(es6语法转es5),但是速度比babel更快

安装依赖

1
npm install @swc/core esbuild @swc/helpers

如果项目需要支持JSX或TSX,安装@swc/helpers,否则无需安装

配置文件

设置tsconfig.json:

1
2
3
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "node"

设置package.json:

1
"type": "module"

config.ts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import esbuild from "esbuild"
import swc from "@swc/core"
import fs from "node:fs"

// 打包的方法,它是个异步方法
await esbuild.build({
entryPoints: ["index.ts"], // 入口
treeShaking: true, // 抖树
bundle: true, // 独立打包
loader: { // loader,后缀名: loader名
".js": "js",
".ts": "ts",
".jsx": "jsx",
".tsx": "tsx"
},
plugins: [ // 插件
{
name: "swc-loader",
// 使用swc
setup(build) {
// args:当前模块的信息
build.onLoad({filter:/\.(js|ts|jsx|tsx)$/},(args)=>{
// 取到了文件里的ts代码
const content = fs.readFileSync(args.path, "utf-8")
// 用swc转换代码,能得到转换后的代码和sourceMap
const {code, map} = swc.transformSync(content, {
filename: args.path
})
return {
contents: code // 返回转换后的代码
}
})
}
}
],
outdir: "dist" , // 出口
})

打包命令:

1
ts-node-esm config.ts

实例

打包工具配置(rollup.config.js):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import ts from "rollup-plugin-typescript2"
import path from "path"
import { fileURLToPath } from "url"
const metaUrl = fileURLToPath(import.meta.url)
const dirName = path.dirname(metaUrl)

export default {
input: "./src/index.ts",
output: {
file: path.resolve(dirName,"./dist/index.js"),
},
plugins:[
ts(),
]
}

LocalStorage过期实例

该库提供将LocalStorage像Cookie一样可限时存储的功能

目录

定义字符串枚举(/src/enum/index.ts):

1
2
3
4
export enum Dictionaries {
permanent = "permanent",
expire = "__expire__"
}

定义类型(/src/type/index.ts):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { Dictionaries } from "../enum"

export type Key = string
export type Expire = Dictionaries.permanent | number // number指时间戳

export interface Data<T> {
value:T,
[Dictionaries.expire]:Expire
}
export interface Result<T> {
message:string,
value:T|null
}
export interface StorageCls {
get:<T>(key:Key)=>Result<T>
set:<T>(key:Key,value:T,expire:Expire)=>void
remove:(key:Key)=>void
clear:()=>void
}

核心代码(/src/index.ts):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// expire:过期时间key   permanent:永不过期
import { StorageCls, Key, Expire, Data, Result } from "./type"
import { Dictionaries } from "./enum"

export class Storage implements StorageCls {
set<T>(key:Key, value:T, expire:Expire=Dictionaries.permanent) {
const data = {
value, // 真正的值
[Dictionaries.expire]:expire // 过期时间
}
// 存入
localStorage.setItem(key,JSON.stringify(data))
}

get<T>(key:Key):Result<T> {
const value = localStorage.getItem(key)
// 判断是否有该key的存值
if(value) {
const data:Data<T> = JSON.parse(value)
const now = new Date().getTime()
if(typeof data[Dictionaries.expire] === "number" && data[Dictionaries.expire] < now) {
// 如果有设置过期时间,且值已过期
this.remove(key)
return {
message: `${key}已过期`,
value: null
}
} else {
// 没有过期,正常返回
return {
message: "取值成功",
value: data.value
}
}
} else {
// 没有该key的存值
return {
message: `${key}无效`,
value: null
}
}
}

remove(key:Key) {
localStorage.removeItem(key)
}

clear() {
localStorage.clear()
}
}

发布订阅模式实例

目录

定义类型(/src/type/index.ts):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export interface Event {
// 绑定事件,当name事件被触发时,调用回调函数
on:(name:string,fn:Function)=>void
// 触发name事件,并传一些参数
emit:(name:string,...args:any[])=>void
// 解除name事件对某回调函数的绑定
off:(name:string,fu:Function)=>void
// 绑定事件,但只执行一次
once:(name:string,fn:Function)=>void
}

export interface List {
// 事件名:事件的回调函数数组
[key:string]:Function[]
}

核心代码(/src/index.ts):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import { Event, List } from "./type"

class Dispatch implements Event {
list:List
constructor() {
this.list = {}
}
on(name:string,fn:Function) {
// 如果已经注册了name,能取到一个数组,否则是undefined
const cbs = this.list[name] || []
// 将回调函数fn加入回调函数数组中
cbs.push(fn)
this.list[name] = cbs
}
emit(name:string,...args:any[]) {
const cbs = this.list[name]
if(cbs) {
// 逐一调用回调函数并传参
cbs.forEach(cb=> {
cb.apply(this,args)
})
} else {
console.error(`没有注册${name}事件`)
}
}
off(name:string,fn:Function) {
const cbs = this.list[name]
if(cbs && fn) {
// 寻找fn并删除
const index = cbs.findIndex(fns=>fns===fn)
cbs.splice(index,1)
} else {
console.error(`没有注册${name}事件或函数有误`)
}
}
once(name:string,fn:Function) {
// 使用临时函数temp包装传入的函数
const temp = (...args:any[])=> {
fn.apply(this,args)
// 调用一次回调后就会解绑
this.off(name,temp)
}
this.on(name,temp)
}
}