1 | var fs = require('fs/promises')
|
2 | var path = require("path")
|
3 | var jeye = require("jeye")
|
4 | var ora = require("ora")
|
5 | var chalk = require("chalk")
|
6 | var { premove } = require("premove")
|
7 | var { mkdir } = require("mk-dirs/sync")
|
8 | const { extname } = require("path")
|
9 | require = require("esm")(module)
|
10 |
|
11 | class Spinner{
|
12 | constructor(logo_art='◸/◿', loading_art=['◸-◿','◸\\◿','◸|◿','◸/◿'], error_art='◸x◿', success_art='◸✓◿'){
|
13 | Object.assign(this, { logo_art, error_art, success_art })
|
14 | this.s = ora({
|
15 | text: "initializing...",
|
16 | spinner: {
|
17 | interval: 80,
|
18 | frames: loading_art
|
19 | },
|
20 | color: 'yellow'
|
21 | })
|
22 | }
|
23 | ready(text="ready"){
|
24 | this.s.stopAndPersist({
|
25 | symbol: chalk.green(this.logo_art),
|
26 | text
|
27 | })
|
28 | }
|
29 | start(text="loading..."){
|
30 | this.s.start(text)
|
31 | }
|
32 | error(text="generic routo error"){
|
33 | this.s.stopAndPersist({
|
34 | symbol: chalk.red(this.error_art),
|
35 | text
|
36 | })
|
37 | }
|
38 | success(text="success"){
|
39 | this.s.stopAndPersist({
|
40 | symbol: chalk.green(this.success_art),
|
41 | text
|
42 | })
|
43 | }
|
44 | }
|
45 |
|
46 | class Routo{
|
47 | constructor(sources, destination, { ignore, watch, silent, config }){
|
48 | Object.assign(this, { sources, destination, silent, ignore })
|
49 | let configData = {}
|
50 | if(config && typeof config === 'string'){
|
51 | try{
|
52 | configData = require(path.join(process.cwd(), config)).default
|
53 | } catch(e){
|
54 | this.error("Invalid config path")
|
55 | }
|
56 | }
|
57 | this.config = {
|
58 | builders: [],
|
59 | transforms: [],
|
60 | ...configData
|
61 | }
|
62 | this.builders = [...this.config.builders, {
|
63 | match: (p) => {
|
64 |
|
65 | return /([\w-*]+)(\.[\w-*]+\.js)/g.exec(p)
|
66 | },
|
67 | build: async (p, { id }) => {
|
68 | let abs_path = path.join(process.cwd(), p)
|
69 | delete require.cache[abs_path]
|
70 | let data = await Promise.resolve(require(abs_path).default)
|
71 |
|
72 | id = id.substr(0,id.length - 3)
|
73 |
|
74 | if(extname(id) === '.html' && id !== '/index.html'){
|
75 | id = id.replace('.html', '/index.html')
|
76 | }
|
77 | return {
|
78 | [id]: data
|
79 | }
|
80 | }
|
81 | }]
|
82 | this.transforms = this.config.builders || []
|
83 | if(watch){
|
84 | this.spinner = new Spinner()
|
85 | this.watch()
|
86 | }
|
87 | }
|
88 |
|
89 | watch(){
|
90 | this.spinner.start('initializing routo...')
|
91 | jeye.watch(this.sources, { ignore: this.ignore })
|
92 | .on("change", async (p, info, changed=[]) => {
|
93 | changed.forEach(c => {
|
94 | let abs_path = path.join(process.cwd(), c)
|
95 | delete require.cache[abs_path]
|
96 | });
|
97 | if(!this.silent && !this.spinner.isSpinning){
|
98 | this.spinner.start('building...')
|
99 | this.loadStart = Date.now()
|
100 | }
|
101 | try{
|
102 | await this.buildFile(p, info)
|
103 | } catch(e){
|
104 | this.error("Error building file: " + p)
|
105 | console.log(e)
|
106 | }
|
107 | return
|
108 | })
|
109 | .on("aggregate", async (targets, changed) => {
|
110 | try{
|
111 |
|
112 | } catch(e){
|
113 | this.error("Error with aggregate build")
|
114 | console.log(e)
|
115 | return;
|
116 | }
|
117 | if(!this.silent){
|
118 | let elapsed = Date.now() - this.loadStart
|
119 | let succeedMessage = () => this.spinner.success(`${changed.length} file${changed.length === 1 ? '' : 's'} changed in ${elapsed}ms`)
|
120 | if(elapsed < 300){
|
121 | setTimeout(succeedMessage, 300 - elapsed)
|
122 | } else {
|
123 | succeedMessage()
|
124 | }
|
125 | }
|
126 | return
|
127 | })
|
128 | .on("ready", (targets) => {
|
129 | this.build().then(() => {
|
130 | if(!this.silent){
|
131 | this.spinner.ready('routo is ready')
|
132 | }
|
133 | })
|
134 | })
|
135 | .on("error", (message) => {
|
136 | this.error(message)
|
137 | })
|
138 | .on("remove", (p) => {
|
139 |
|
140 |
|
141 |
|
142 | })
|
143 | }
|
144 |
|
145 | error(message){
|
146 | if(!this.silent){
|
147 | if(this.spinner){
|
148 | this.spinner.error(message)
|
149 | } else {
|
150 | console.log(`${chalk.red('◸x◿')} ${message}`)
|
151 | }
|
152 | } else {
|
153 | throw message;
|
154 | }
|
155 | }
|
156 |
|
157 | isBuilder(p){
|
158 | let segments = path.basename(p).split('.')
|
159 | return segments.length > 2 && segments[segments.length - 1] === 'js'
|
160 | }
|
161 |
|
162 | async buildFile(p, info){
|
163 |
|
164 |
|
165 | async function copyBuilder(p, {id}){
|
166 | let data = await fs.readFile(p)
|
167 | return { [id]: data }
|
168 | }
|
169 |
|
170 |
|
171 | let builder = this.builders.find(b =>
|
172 | (!b.match || (typeof b.match === 'function' && b.match(p)))
|
173 | && typeof b.build === 'function'
|
174 | )
|
175 | builder = builder ? builder.build : copyBuilder
|
176 | let output = await builder(p, info)
|
177 | let promises = Object.keys(output).map(async k => {
|
178 | let output_path = path.join(this.destination, k)
|
179 | let ensured_folder = output_path.replace(path.basename(output_path),"")
|
180 | mkdir(ensured_folder)
|
181 | await fs.writeFile(output_path, output[k])
|
182 | })
|
183 |
|
184 | await Promise.all(promises)
|
185 | return;
|
186 | }
|
187 |
|
188 | async aggregate(targets, changed = null){
|
189 | if(changed === null){
|
190 |
|
191 | } else {
|
192 |
|
193 | }
|
194 | return;
|
195 | }
|
196 |
|
197 | async build(){
|
198 | try{
|
199 | let targets = await jeye.targets(this.sources, { ignore: this.ignore })
|
200 | await Promise.all(
|
201 | Object.keys(targets).map(async p => {
|
202 | try{
|
203 | await this.buildFile(p, targets[p])
|
204 | } catch(e){
|
205 | this.error(p + ": " + e)
|
206 | }
|
207 | })
|
208 | )
|
209 | await this.aggregate(targets)
|
210 | return Object.keys(targets)
|
211 | } catch(e){
|
212 | this.error(e)
|
213 | }
|
214 | }
|
215 |
|
216 | }
|
217 |
|
218 | module.exports = Routo |
\ | No newline at end of file |