@vindo/cli 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/builder/index.js +240 -0
- package/bin/{server.js → builder/server.js} +33 -2
- package/bin/builder/template.js +43 -0
- package/bin/config.js +108 -58
- package/bin/index.js +47 -42
- package/bin/option.json +46 -0
- package/bin/{util.js → util/index.js} +43 -62
- package/package.json +4 -3
- package/bin/build.js +0 -172
- package/bin/template.js +0 -29
- /package/bin/{development.js → builder/development.js} +0 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
|
|
2
|
+
const es = require('esbuild')
|
|
3
|
+
const sass = require('sass')
|
|
4
|
+
const util = require('../util')
|
|
5
|
+
const config = require('../config')
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
module.exports = exports = Object.create(util, {
|
|
9
|
+
config: {
|
|
10
|
+
value: config
|
|
11
|
+
}
|
|
12
|
+
})
|
|
13
|
+
const {file, string, resolve} = util
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Get basename
|
|
18
|
+
* @param {string} file
|
|
19
|
+
*/
|
|
20
|
+
function getname(val) {
|
|
21
|
+
return file.path.parse(val).name
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get chunk
|
|
27
|
+
* @param {string} dir
|
|
28
|
+
*/
|
|
29
|
+
async function getChunk(dir) {
|
|
30
|
+
var code = []
|
|
31
|
+
var files = await file.readdir(dir, {recursive: true})
|
|
32
|
+
|
|
33
|
+
for(var {name, parentPath} of files) {
|
|
34
|
+
if(!/\.(js|tsx|jsx)$/.test(name)) {
|
|
35
|
+
continue
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
var name = getname(name)
|
|
39
|
+
if(name) {
|
|
40
|
+
var _file = file.path.join(parentPath, name)
|
|
41
|
+
|
|
42
|
+
if(name == 'index') {
|
|
43
|
+
_file = parentPath
|
|
44
|
+
name = getname(parentPath)
|
|
45
|
+
}
|
|
46
|
+
code.push(`import ${name} from '${_file}'`, `data.${name} = ${name}`)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return code
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Get imported files
|
|
55
|
+
* @param {object} opt
|
|
56
|
+
* @param {string} data
|
|
57
|
+
*/
|
|
58
|
+
async function getImports(entry, data) {
|
|
59
|
+
var imp = []
|
|
60
|
+
var matches = data.matchAll(/import\s(.*)\sfrom.*('|"|\/)([a-zA-Z0-9_-]+)('|")/g)
|
|
61
|
+
|
|
62
|
+
for(var item of matches) {
|
|
63
|
+
if(/(\.\/|)components\//.test(item[0])) {
|
|
64
|
+
imp.push(`var ${item[1]} = ${item[3]}`)
|
|
65
|
+
}
|
|
66
|
+
else if(/(\.\/)/.test(item[0])) {
|
|
67
|
+
imp.push(item[0].replace(/('|")(\.\/)/, `$1${resolve.dirname(entry)}/`))
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
imp.push(item[0])
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return imp
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Get all imports from components directory
|
|
79
|
+
* @param {object} opt
|
|
80
|
+
*/
|
|
81
|
+
async function getComponents(opt) {
|
|
82
|
+
var data = []
|
|
83
|
+
|
|
84
|
+
if(Array.isArray(opt.includes)) {
|
|
85
|
+
for(var item of opt.includes) {
|
|
86
|
+
const dir = file.path.resolve(process.cwd(), 'node_modules', item)
|
|
87
|
+
if(!file.exists(dir)) {
|
|
88
|
+
continue
|
|
89
|
+
}
|
|
90
|
+
data = data.concat(await getChunk(dir))
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return data.concat(await getChunk(opt.components))
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Collect components and assemble before building
|
|
100
|
+
*
|
|
101
|
+
* @param {object} opt option
|
|
102
|
+
*/
|
|
103
|
+
async function startBuild(opt) {
|
|
104
|
+
|
|
105
|
+
if(!file.exists(opt.entry)) {
|
|
106
|
+
throw new Error(`Entry file not found. "${opt.entry}"`)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const data = file.read(opt.entry)
|
|
110
|
+
const temp = file.read(opt.template)
|
|
111
|
+
|
|
112
|
+
const impo = await getImports(opt.entry, data)
|
|
113
|
+
const comp = await getComponents(opt)
|
|
114
|
+
|
|
115
|
+
const code = string.replace(temp, {
|
|
116
|
+
REACT_COMPONENTS: comp.join('\n'),
|
|
117
|
+
IMPORTED_COMPONENTS: impo.join('\n'),
|
|
118
|
+
HEAD_COMPONENT: data.match(/<head.*>((.|\n)*)<\/head>/g)[0],
|
|
119
|
+
BODY_COMPONENT: data.match(/<body.*>((.|\n)*)<\/body>/g)[0]
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
await file.promises.writeFile(opt.chunk, code, {flag: 'w'})
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Create a bundle file
|
|
128
|
+
*
|
|
129
|
+
* @param {object} opt option
|
|
130
|
+
*/
|
|
131
|
+
async function startBundle(opt) {
|
|
132
|
+
|
|
133
|
+
if(!file.exists(opt.chunk)) {
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const build = await es.build({
|
|
138
|
+
...opt.settings,
|
|
139
|
+
minify: opt.minify,
|
|
140
|
+
outfile: opt.outfile,
|
|
141
|
+
entryPoints: [opt.chunk],
|
|
142
|
+
})
|
|
143
|
+
const meta = Object.keys(build.metafile.outputs)[0]
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Append bundle hash to vindo config
|
|
147
|
+
*/
|
|
148
|
+
const name = opt.bundleName
|
|
149
|
+
const hash = await config.bundle(opt.name ?? name, [meta.match(/-(.*).js/)[1], name])
|
|
150
|
+
/**
|
|
151
|
+
* Delete old bundle
|
|
152
|
+
*/
|
|
153
|
+
const files = file.readdir(resolve.root(opt.output))
|
|
154
|
+
files.forEach((item) => {
|
|
155
|
+
const matched = item.name.match(
|
|
156
|
+
new RegExp(`^${name}-([A-Z0-9+]{8})\.js$`)
|
|
157
|
+
)
|
|
158
|
+
if(matched) {
|
|
159
|
+
if(hash == matched[1]) {
|
|
160
|
+
return
|
|
161
|
+
}
|
|
162
|
+
file.unlink(file.path.resolve(item.parentPath, item.name))
|
|
163
|
+
}
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
if(file.exists(opt.chunk)) {
|
|
167
|
+
file.unlink(opt.chunk)
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Compile scss file
|
|
174
|
+
* @param {object} opt
|
|
175
|
+
*/
|
|
176
|
+
async function startCompile(opt) {
|
|
177
|
+
if(!opt.compileSass) {
|
|
178
|
+
return
|
|
179
|
+
}
|
|
180
|
+
const input = resolve.root(opt.compileSass.input)
|
|
181
|
+
const output = resolve.root(opt.compileSass.output)
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Create output dir if not exists
|
|
185
|
+
*/
|
|
186
|
+
if(!file.exists(input)) {
|
|
187
|
+
throw new Error(`No sass input directory found. "${input}"\n`)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Create output dir if not exists
|
|
192
|
+
*/
|
|
193
|
+
if(!file.exists(output)) {
|
|
194
|
+
await file.makedir(output)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function scss(name) {
|
|
198
|
+
return resolve.root(opt.compileSass.input, name)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
const files = await file.readdir(input)
|
|
203
|
+
for(var item of files) {
|
|
204
|
+
|
|
205
|
+
if(item.isFile()) {
|
|
206
|
+
const url = scss(item.name)
|
|
207
|
+
|
|
208
|
+
if(file.exists(url)) {
|
|
209
|
+
const css = sass.compile(url).css
|
|
210
|
+
|
|
211
|
+
const out = resolve.root(
|
|
212
|
+
output,
|
|
213
|
+
item.name.replace(/\.scss/, '.css')
|
|
214
|
+
)
|
|
215
|
+
await file.promises.writeFile(out, css, {flag: 'w'})
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
*
|
|
224
|
+
* @param {object} config
|
|
225
|
+
*/
|
|
226
|
+
exports.build = async function build(opt) {
|
|
227
|
+
/**
|
|
228
|
+
* Create build directory
|
|
229
|
+
*/
|
|
230
|
+
await file.makedir(resolve.root(opt.output))
|
|
231
|
+
|
|
232
|
+
try {
|
|
233
|
+
await startBuild(opt)
|
|
234
|
+
await startBundle(opt)
|
|
235
|
+
await startCompile(opt)
|
|
236
|
+
}
|
|
237
|
+
catch(e) {
|
|
238
|
+
console.log(e)
|
|
239
|
+
}
|
|
240
|
+
}
|
|
@@ -2,8 +2,8 @@ const {parse} = require('node:path')
|
|
|
2
2
|
const {createServer} = require('node:http')
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
module.exports = function(cb) {
|
|
6
5
|
|
|
6
|
+
function server(cb) {
|
|
7
7
|
return createServer((req, res) => {
|
|
8
8
|
const url = parse(req.url)
|
|
9
9
|
|
|
@@ -37,7 +37,38 @@ module.exports = function(cb) {
|
|
|
37
37
|
}
|
|
38
38
|
res.headers({'Access-Control-Allow-Origin': '*'})
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
|
|
41
41
|
cb(req, res)
|
|
42
42
|
})
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
module.exports = function(builder, cb) {
|
|
47
|
+
var file = builder.file
|
|
48
|
+
var events = builder.events
|
|
49
|
+
var resolve = builder.resolve
|
|
50
|
+
var option = builder.config.option
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Run development server and create bundle
|
|
54
|
+
*/
|
|
55
|
+
const http = server(async function(req, res) {
|
|
56
|
+
const data = await file.read(resolve.clidir('builder/development.js'))
|
|
57
|
+
|
|
58
|
+
if(req.isStream()) {
|
|
59
|
+
events.on('change', () => {
|
|
60
|
+
res.write(`data: {changed: true}\n\n`)
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if(req.is('development')) {
|
|
65
|
+
res.headers({
|
|
66
|
+
'Content-Type':
|
|
67
|
+
'application/javascript'
|
|
68
|
+
})
|
|
69
|
+
res.end(data)
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
http.listen(option.port, cb)
|
|
43
74
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import * as client from '@vindo/react/client'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* This will be the holder of all components that will be imported
|
|
7
|
+
*/
|
|
8
|
+
const data = {}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Template for components to be imported
|
|
12
|
+
*/
|
|
13
|
+
{REACT_COMPONENTS}
|
|
14
|
+
{IMPORTED_COMPONENTS}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
data.Link = client.Link
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
if(data.head || data.body) {
|
|
21
|
+
throw new SyntaxError(`Component name '${data.head ? 'head' : 'body'}' is a reserved name for SSR client components.`)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
data.head = function head(meta) {
|
|
25
|
+
const {props:{children}} = React.cloneElement((
|
|
26
|
+
{HEAD_COMPONENT}
|
|
27
|
+
))
|
|
28
|
+
return children
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
data.body = function body({meta}) {
|
|
32
|
+
var {props:{children}} = React.cloneElement(({BODY_COMPONENT}))
|
|
33
|
+
|
|
34
|
+
if(!Array.isArray(children)) {
|
|
35
|
+
children = React.Children.toArray(children)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return React.Children.map(children, (child, key) => {
|
|
39
|
+
return React.cloneElement(child, {key, ...arguments[0]})
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
client.render(document, data)
|
package/bin/config.js
CHANGED
|
@@ -1,70 +1,120 @@
|
|
|
1
1
|
const util = require('./util')
|
|
2
|
-
const
|
|
2
|
+
const option = require('./option')
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
get devScript() {
|
|
10
|
-
return `http://127.0.0.1:${this.option.port}/development`
|
|
11
|
-
},
|
|
5
|
+
const file = util.file
|
|
6
|
+
const object = util.object
|
|
7
|
+
const resolve = util.resolve
|
|
8
|
+
const parseArgs = util.parseArgs
|
|
12
9
|
|
|
13
|
-
/**
|
|
14
|
-
* Vindo config
|
|
15
|
-
*/
|
|
16
|
-
get vindo() {
|
|
17
|
-
|
|
18
|
-
return config.config({
|
|
19
|
-
buildOption: {
|
|
20
|
-
port: 2001,
|
|
21
|
-
minify: true,
|
|
22
|
-
watch: [
|
|
23
|
-
'./src'
|
|
24
|
-
],
|
|
25
|
-
output: './public'
|
|
26
|
-
}
|
|
27
|
-
})
|
|
28
|
-
},
|
|
29
10
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
return Object.assign(opt, {
|
|
38
|
-
...args,
|
|
39
|
-
settings: {
|
|
40
|
-
bundle: true,
|
|
41
|
-
format: 'esm',
|
|
42
|
-
logLevel: 'error',
|
|
43
|
-
allowOverwrite: true,
|
|
44
|
-
},
|
|
45
|
-
output: typeof args.output == 'string' ? args.output : opt.output,
|
|
46
|
-
})
|
|
47
|
-
},
|
|
11
|
+
/**
|
|
12
|
+
* Exports
|
|
13
|
+
*/
|
|
14
|
+
module.exports = exports = Object.create({}, {
|
|
15
|
+
args: {get: getArgs},
|
|
16
|
+
option: {get: getOption},
|
|
17
|
+
})
|
|
48
18
|
|
|
49
|
-
/**
|
|
50
|
-
* Get args
|
|
51
|
-
*/
|
|
52
|
-
argv() {
|
|
53
|
-
const args = {}
|
|
54
|
-
const argv = process.argv
|
|
55
19
|
|
|
56
|
-
|
|
57
|
-
|
|
20
|
+
var path = resolve.root('vindo.json')
|
|
21
|
+
if(!file.exists(path)) {
|
|
22
|
+
throw Error(`vindo.json file does not exists.`)
|
|
23
|
+
}
|
|
24
|
+
var vindo = file.get(path)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* To string
|
|
29
|
+
*/
|
|
30
|
+
function toStr(data) {
|
|
31
|
+
return Buffer.from(JSON.stringify(data)).toString('base64').replace(/=/g, '')
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Get arguments
|
|
36
|
+
*/
|
|
37
|
+
function getArgs() {
|
|
38
|
+
const args = process.argv
|
|
39
|
+
|
|
40
|
+
const command = args[2]
|
|
41
|
+
if(!option.commands.includes(command)) {
|
|
42
|
+
throw new ReferenceError('You need to provide at least one command.')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const {values} = parseArgs({
|
|
46
|
+
args: args.slice(3),
|
|
47
|
+
allowPositionals: true,
|
|
48
|
+
options: option.argsOptions,
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
return object.merge(values, {[command]: true, command})
|
|
52
|
+
}
|
|
58
53
|
|
|
59
|
-
if(i < 2) {
|
|
60
|
-
continue
|
|
61
|
-
}
|
|
62
|
-
var arg = argv[i].split(pat, 3).filter(String)
|
|
63
54
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
55
|
+
/**
|
|
56
|
+
* Get build option and arguments
|
|
57
|
+
*/
|
|
58
|
+
function getOption() {
|
|
59
|
+
var opt = object.merge(option.buildOptions, vindo.buildOptions)
|
|
60
|
+
var opt = object.merge(opt, getArgs())
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
...opt,
|
|
64
|
+
name: vindo.name,
|
|
65
|
+
sass: option.sass,
|
|
66
|
+
settings: option.settings,
|
|
67
|
+
|
|
68
|
+
entry: resolve.root(opt.entry),
|
|
69
|
+
chunk: resolve.root(opt.output, 'chunk.jsx'),
|
|
70
|
+
template: resolve.clidir('builder', 'template.js'),
|
|
71
|
+
components: resolve.dirname(opt.entry, 'components'),
|
|
72
|
+
outfile: resolve.root(`${opt.output}/${opt.bundleName}.js`),
|
|
73
|
+
devScript: `http://127.0.0.1:${opt.port}/development`,
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Add bundle
|
|
80
|
+
*/
|
|
81
|
+
exports.add = function add(opt) {
|
|
82
|
+
if(opt.package) {
|
|
83
|
+
var pkg = resolve.current('node_modules', opt.package, 'vindo.json')
|
|
84
|
+
if(!file.exists(pkg)) {
|
|
85
|
+
return
|
|
67
86
|
}
|
|
68
|
-
|
|
87
|
+
|
|
88
|
+
var pkg = file.get(pkg)
|
|
89
|
+
if(pkg) {
|
|
90
|
+
exports.bundle(null, pkg.bundles)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Add bundle metadata to vindo config
|
|
98
|
+
*/
|
|
99
|
+
exports.bundle = async function bundle(name, data) {
|
|
100
|
+
if(!vindo.bundles) {
|
|
101
|
+
vindo.bundles = {}
|
|
69
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* Add new bundle
|
|
105
|
+
*/
|
|
106
|
+
if(name) {
|
|
107
|
+
vindo.bundles[name] = toStr(data)
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Merge to existing bundles
|
|
111
|
+
*/
|
|
112
|
+
else {
|
|
113
|
+
vindo.bundles = object.merge(vindo.bundles, data)
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Update the config file
|
|
117
|
+
*/
|
|
118
|
+
await file.promises.writeFile(path, JSON.stringify(vindo, null, 2), {flag: 'w'})
|
|
119
|
+
return data[0]
|
|
70
120
|
}
|
package/bin/index.js
CHANGED
|
@@ -1,27 +1,28 @@
|
|
|
1
1
|
#! /usr/bin/env node
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const server = require('./server')
|
|
7
|
-
const config = require('./config')
|
|
4
|
+
const builder = require('./builder')
|
|
5
|
+
const server = require('./builder/server')
|
|
8
6
|
|
|
9
|
-
const {watch} = require('chokidar')
|
|
10
7
|
const kill = require('tree-kill')
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const {log, read, events, colors, resolve} = util
|
|
8
|
+
const chokidar = require('chokidar')
|
|
9
|
+
const childProcess = require('node:child_process')
|
|
15
10
|
|
|
16
11
|
|
|
17
12
|
var child = null
|
|
18
|
-
var
|
|
13
|
+
var log = builder.log
|
|
14
|
+
var build = builder.build
|
|
15
|
+
var config = builder.config
|
|
16
|
+
var events = builder.events
|
|
17
|
+
var colors = builder.colors
|
|
18
|
+
var option = builder.config.option
|
|
19
|
+
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* Set to global environment
|
|
22
23
|
*/
|
|
23
|
-
process.env.
|
|
24
|
-
process.env.
|
|
24
|
+
process.env.DEV_PORT = option.port
|
|
25
|
+
process.env.DEV_SERVER = option.devScript
|
|
25
26
|
|
|
26
27
|
|
|
27
28
|
/**
|
|
@@ -32,8 +33,7 @@ async function watcher(dirs, cb) {
|
|
|
32
33
|
if(!Array.isArray(dirs)) {
|
|
33
34
|
throw TypeError('Expected a value of type `Array` but received a `String`.')
|
|
34
35
|
}
|
|
35
|
-
watch(dirs, {
|
|
36
|
-
// ignored: (path, stats) => stats?.isFile() && !path.endsWith('.js'), // only watch js files
|
|
36
|
+
chokidar.watch(dirs, {
|
|
37
37
|
persistent: true
|
|
38
38
|
})
|
|
39
39
|
.on('change', (path) => cb({path}))
|
|
@@ -47,13 +47,18 @@ async function watcher(dirs, cb) {
|
|
|
47
47
|
/**
|
|
48
48
|
*
|
|
49
49
|
*/
|
|
50
|
-
function execute() {
|
|
50
|
+
function execute(cmd) {
|
|
51
51
|
var i = 0
|
|
52
|
-
|
|
52
|
+
|
|
53
|
+
if(Array.isArray(cmd)) {
|
|
54
|
+
cmd = cmd.join(' && ')
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
var sp = childProcess.exec(cmd, {
|
|
53
58
|
env: process.env,
|
|
54
59
|
cwd: process.cwd(),
|
|
55
60
|
})
|
|
56
|
-
log('green', `[vindo] Running ${
|
|
61
|
+
log('green', `[vindo] Running ${cmd}`)
|
|
57
62
|
|
|
58
63
|
/**
|
|
59
64
|
* Emit change event to reload client
|
|
@@ -94,7 +99,7 @@ function execute() {
|
|
|
94
99
|
* Restart server and rebuild
|
|
95
100
|
*/
|
|
96
101
|
function restart(event) {
|
|
97
|
-
log('
|
|
102
|
+
log('green', `[vindo] File changed '${event.path}'`)
|
|
98
103
|
log('green', `[vindo] Rebuilding...`)
|
|
99
104
|
log('green', `[vindo] Restarting server...`)
|
|
100
105
|
|
|
@@ -103,36 +108,18 @@ function restart(event) {
|
|
|
103
108
|
if(child) {
|
|
104
109
|
kill(child.pid)
|
|
105
110
|
}
|
|
106
|
-
child = execute()
|
|
111
|
+
child = execute(option.execute)
|
|
107
112
|
}
|
|
108
113
|
|
|
109
114
|
|
|
110
115
|
/**
|
|
111
|
-
*
|
|
116
|
+
* Start building and execute
|
|
112
117
|
*/
|
|
113
|
-
|
|
114
|
-
if(req.isStream()) {
|
|
115
|
-
events.on('change', () => {
|
|
116
|
-
res.write(`data: {changed: true}\n\n`)
|
|
117
|
-
})
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if(req.is('development')) {
|
|
121
|
-
res.headers({
|
|
122
|
-
'Content-Type':
|
|
123
|
-
'application/javascript'
|
|
124
|
-
})
|
|
125
|
-
res.end(await read(resolve.curr('development.js')))
|
|
126
|
-
}
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
http.listen(option.port, function() {
|
|
132
|
-
child = execute()
|
|
133
|
-
|
|
118
|
+
function start() {
|
|
134
119
|
build(option)
|
|
135
120
|
|
|
121
|
+
child = execute(option.execute)
|
|
122
|
+
|
|
136
123
|
if(option.watch) {
|
|
137
124
|
log('yellow', '[vindo] v0.0.1')
|
|
138
125
|
log('yellow', `[vindo] Watching at ${option.watch}`)
|
|
@@ -140,4 +127,22 @@ http.listen(option.port, function() {
|
|
|
140
127
|
|
|
141
128
|
watcher(option.watch, restart)
|
|
142
129
|
}
|
|
143
|
-
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Command specific tasks
|
|
135
|
+
*/
|
|
136
|
+
switch(option.command) {
|
|
137
|
+
case 'add':
|
|
138
|
+
config.add(option)
|
|
139
|
+
break
|
|
140
|
+
|
|
141
|
+
case 'run':
|
|
142
|
+
server(builder, start)
|
|
143
|
+
break
|
|
144
|
+
|
|
145
|
+
case 'build':
|
|
146
|
+
build(option)
|
|
147
|
+
break
|
|
148
|
+
}
|
package/bin/option.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"buildOptions": {
|
|
3
|
+
"port": 2001,
|
|
4
|
+
"minify": true,
|
|
5
|
+
"watch": [
|
|
6
|
+
"./src"
|
|
7
|
+
],
|
|
8
|
+
"output": "./public",
|
|
9
|
+
"bundleName": "main"
|
|
10
|
+
},
|
|
11
|
+
"sass": {
|
|
12
|
+
"style": "compressed"
|
|
13
|
+
},
|
|
14
|
+
"settings": {
|
|
15
|
+
"bundle": true,
|
|
16
|
+
"metafile": true,
|
|
17
|
+
"format": "esm",
|
|
18
|
+
"jsx": "automatic",
|
|
19
|
+
"logLevel": "error",
|
|
20
|
+
"outfile": "dist/bundle.js",
|
|
21
|
+
"loader": {
|
|
22
|
+
".js": "jsx",
|
|
23
|
+
".jsx": "jsx",
|
|
24
|
+
".ts": "tsx",
|
|
25
|
+
".tsx": "tsx"
|
|
26
|
+
},
|
|
27
|
+
"entryNames": "[name]-[hash]",
|
|
28
|
+
"allowOverwrite": true,
|
|
29
|
+
"jsxImportSource": "react"
|
|
30
|
+
},
|
|
31
|
+
"commands": [
|
|
32
|
+
"run",
|
|
33
|
+
"add",
|
|
34
|
+
"build",
|
|
35
|
+
"bundle"
|
|
36
|
+
],
|
|
37
|
+
"argsOptions": {
|
|
38
|
+
"add": {"type": "string"},
|
|
39
|
+
"port": {"type": "string"},
|
|
40
|
+
"package": {"type": "string"},
|
|
41
|
+
"output": {"type": "string"},
|
|
42
|
+
"mergeTo": {"type": "string"},
|
|
43
|
+
"execute": {"type": "string"},
|
|
44
|
+
"bundleName": {"type": "string"}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -1,19 +1,13 @@
|
|
|
1
|
-
const
|
|
2
|
-
const {file, events, string} = require('@vindo/utility')
|
|
1
|
+
const util = require('util')
|
|
2
|
+
const {file, events, string, object} = require('@vindo/utility')
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
module.exports = exports = Object.create(file, {
|
|
7
|
-
string: {value: string},
|
|
8
|
-
events: {value: events},
|
|
9
|
-
})
|
|
10
|
-
|
|
11
5
|
/**
|
|
12
6
|
* Set colors
|
|
13
7
|
*/
|
|
14
8
|
const colors = {
|
|
15
9
|
set(name, text) {
|
|
16
|
-
return
|
|
10
|
+
return util.styleText(name, text)
|
|
17
11
|
},
|
|
18
12
|
colorize(text) {
|
|
19
13
|
var text = text.toString()
|
|
@@ -31,46 +25,47 @@ const colors = {
|
|
|
31
25
|
}
|
|
32
26
|
|
|
33
27
|
for(var item of Object.values(pattern)) {
|
|
34
|
-
text = text.replace(item.pat,
|
|
28
|
+
text = text.replace(item.pat, util.styleText(item.color, item.text))
|
|
35
29
|
}
|
|
36
30
|
return text.trim()
|
|
37
31
|
}
|
|
38
32
|
}
|
|
39
33
|
|
|
40
|
-
function replace(string, data = {}) {
|
|
41
|
-
if(Object.keys(data).length == 0) {
|
|
42
|
-
return string
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function repl(v) {
|
|
46
|
-
var value = data[v.match(/([a-z_]+)/g)]
|
|
47
|
-
if(value) {
|
|
48
|
-
return value
|
|
49
|
-
}
|
|
50
|
-
return ''
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if(string) {
|
|
54
|
-
var patt = string.match(/(\{[a-z_]+\})/g)
|
|
55
|
-
if(patt) {
|
|
56
|
-
return string.replace(new RegExp(patt.join('|'), 'g'), repl)
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return string
|
|
60
|
-
}
|
|
61
|
-
|
|
62
34
|
/**
|
|
63
35
|
* Resolve path
|
|
64
36
|
*/
|
|
65
37
|
const resolve = {
|
|
66
|
-
|
|
67
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Resolve either a root directory
|
|
40
|
+
* of where a command executed or the current working directory.
|
|
41
|
+
*/
|
|
42
|
+
root(...args) {
|
|
43
|
+
var path = process.env.INIT_CWD
|
|
44
|
+
if(!path) {
|
|
45
|
+
path = process.cwd()
|
|
46
|
+
}
|
|
47
|
+
return file.path.resolve(path, ...args)
|
|
68
48
|
},
|
|
69
|
-
|
|
70
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Resolve files of this cli package.
|
|
51
|
+
*/
|
|
52
|
+
clidir(...args) {
|
|
53
|
+
return file.path.resolve(file.path.dirname(__dirname), ...args)
|
|
71
54
|
},
|
|
55
|
+
/**
|
|
56
|
+
* Resolve file from current directory.
|
|
57
|
+
*/
|
|
72
58
|
dirname(path, ...args) {
|
|
73
|
-
|
|
59
|
+
if(!path) {
|
|
60
|
+
return
|
|
61
|
+
}
|
|
62
|
+
return resolve.root(file.path.dirname(path), ...args)
|
|
63
|
+
},
|
|
64
|
+
/**
|
|
65
|
+
* Resolve current working directory
|
|
66
|
+
*/
|
|
67
|
+
current(...args) {
|
|
68
|
+
return file.path.resolve(process.cwd(), ...args)
|
|
74
69
|
},
|
|
75
70
|
}
|
|
76
71
|
|
|
@@ -79,42 +74,25 @@ const resolve = {
|
|
|
79
74
|
*/
|
|
80
75
|
function log(name, text) {
|
|
81
76
|
if(name) {
|
|
82
|
-
return console.log(
|
|
77
|
+
return console.log(util.styleText(name, text.toString().trim()))
|
|
83
78
|
}
|
|
84
79
|
return console.log(text)
|
|
85
80
|
}
|
|
86
81
|
|
|
87
|
-
/**
|
|
88
|
-
* Use module
|
|
89
|
-
*/
|
|
90
|
-
function use(name) {
|
|
91
|
-
return require(resolve.main('node_modules', name))
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
*
|
|
96
|
-
* File system utilities
|
|
97
|
-
*
|
|
98
|
-
*/
|
|
99
|
-
async function copy(dest) {
|
|
100
|
-
const src = resolve.curr(
|
|
101
|
-
path.basename(dest)
|
|
102
|
-
)
|
|
103
|
-
return await file.promises.cp(src, dest)
|
|
104
|
-
}
|
|
105
82
|
/**
|
|
106
83
|
*
|
|
107
84
|
*/
|
|
108
|
-
async function makedir(path) {
|
|
85
|
+
file.makedir = async function makedir(path) {
|
|
109
86
|
try {
|
|
110
87
|
await file.promises.mkdir(path)
|
|
111
88
|
}
|
|
112
89
|
catch(e) {}
|
|
113
90
|
}
|
|
91
|
+
|
|
114
92
|
/**
|
|
115
93
|
*
|
|
116
94
|
*/
|
|
117
|
-
async function unlink(path) {
|
|
95
|
+
file.unlink = async function unlink(path) {
|
|
118
96
|
try {
|
|
119
97
|
await file.promises.unlink(path)
|
|
120
98
|
}
|
|
@@ -122,10 +100,13 @@ async function unlink(path) {
|
|
|
122
100
|
}
|
|
123
101
|
|
|
124
102
|
|
|
103
|
+
module.exports = exports = Object.create(util, {
|
|
104
|
+
file: {value: file},
|
|
105
|
+
string: {value: string},
|
|
106
|
+
object: {value: object},
|
|
107
|
+
events: {value: events},
|
|
108
|
+
})
|
|
109
|
+
|
|
125
110
|
exports.log = log
|
|
126
|
-
exports.use = use
|
|
127
|
-
exports.copy = copy
|
|
128
|
-
exports.unlink = unlink
|
|
129
|
-
exports.makedir = makedir
|
|
130
111
|
exports.colors = colors
|
|
131
112
|
exports.resolve = resolve
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vindo/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "Command line for running vindo server and react SSR for development.",
|
|
5
5
|
"main": "bin/index.js",
|
|
6
6
|
"publishConfig": {
|
|
@@ -24,9 +24,10 @@
|
|
|
24
24
|
},
|
|
25
25
|
"homepage": "https://github.com/rmindo/vindo/tree/main/packages/cli",
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@vindo/utility": "^0.0.
|
|
27
|
+
"@vindo/utility": "^0.0.13",
|
|
28
28
|
"chokidar": "^4.0.3",
|
|
29
|
-
"esbuild": "^0.
|
|
29
|
+
"esbuild": "^0.27.3",
|
|
30
|
+
"sass": "^1.98.0",
|
|
30
31
|
"tree-kill": "^1.2.2"
|
|
31
32
|
}
|
|
32
33
|
}
|
package/bin/build.js
DELETED
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
const es = require('esbuild')
|
|
3
|
-
const {
|
|
4
|
-
path,
|
|
5
|
-
read,
|
|
6
|
-
exists,
|
|
7
|
-
string,
|
|
8
|
-
unlink,
|
|
9
|
-
makedir,
|
|
10
|
-
resolve,
|
|
11
|
-
readdir,
|
|
12
|
-
promises,
|
|
13
|
-
}
|
|
14
|
-
= require('./util')
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Get basename
|
|
19
|
-
* @param {string} file
|
|
20
|
-
*/
|
|
21
|
-
function getname(file) {
|
|
22
|
-
return path.parse(file).name
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Get imported files
|
|
27
|
-
* @param {object} opt
|
|
28
|
-
* @param {string} data
|
|
29
|
-
*/
|
|
30
|
-
async function getImports(entry, data) {
|
|
31
|
-
var imp = []
|
|
32
|
-
var matches = data.matchAll(/import\s(.*)\sfrom.*('|"|\/)([a-zA-Z_-]+)('|")/g)
|
|
33
|
-
|
|
34
|
-
for(var item of matches) {
|
|
35
|
-
if(/(\.\/|)components\//.test(item[0])) {
|
|
36
|
-
imp.push(`var ${item[1]} = ${item[3]}`)
|
|
37
|
-
}
|
|
38
|
-
else if(/(\.\/)/.test(item[0])) {
|
|
39
|
-
imp.push(item[0].replace(/('|")(\.\/)/, `$1${resolve.dirname(entry)}/`))
|
|
40
|
-
}
|
|
41
|
-
else {
|
|
42
|
-
imp.push(item[0])
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
return imp
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Get all codes from components
|
|
51
|
-
* @param {object} opt
|
|
52
|
-
*/
|
|
53
|
-
async function getComponents(opt) {
|
|
54
|
-
var files = await readdir(opt.components, {recursive: true})
|
|
55
|
-
var files = files.filter(file => {
|
|
56
|
-
return /\.(tsx|jsx)$/.test(file.name)
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
var code = []
|
|
60
|
-
|
|
61
|
-
for(var {name, parentPath} of files) {
|
|
62
|
-
var name = getname(name)
|
|
63
|
-
if(name) {
|
|
64
|
-
var file = path.join(parentPath, name)
|
|
65
|
-
|
|
66
|
-
if(name == 'index') {
|
|
67
|
-
file = parentPath
|
|
68
|
-
name = getname(parentPath)
|
|
69
|
-
}
|
|
70
|
-
code.push(`import ${name} from '${file}'`, `chunk.${name} = ${name}`)
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return code
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Collect components and assemble before building
|
|
79
|
-
*
|
|
80
|
-
* @param {object} opt option
|
|
81
|
-
*/
|
|
82
|
-
async function build(opt) {
|
|
83
|
-
const data = read(opt.entry)
|
|
84
|
-
const temp = read(opt.template)
|
|
85
|
-
|
|
86
|
-
const impo = await getImports(opt.entry, data)
|
|
87
|
-
const comp = await getComponents(opt)
|
|
88
|
-
|
|
89
|
-
const code = string.replace(temp, {
|
|
90
|
-
CHUNK_IMPORTS: comp.join('\n'),
|
|
91
|
-
ENTRY_IMPORTS: impo.join('\n'),
|
|
92
|
-
HEAD_COMPONENT: data.match(/<head.*>((.|\n)*)<\/head>/g)[0],
|
|
93
|
-
BODY_COMPONENT: data.match(/<Provider.*>((.|\n)*)<\/Provider>/g)[0]
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
await promises.writeFile(opt.chunk, code, {flag: 'w'})
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Create a bundle file
|
|
102
|
-
*
|
|
103
|
-
* @param {object} opt option
|
|
104
|
-
*/
|
|
105
|
-
async function bundle(opt) {
|
|
106
|
-
if(!exists(opt.chunk)) {
|
|
107
|
-
return
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const {metafile} = await es.build({
|
|
111
|
-
...opt.settings,
|
|
112
|
-
metafile: true,
|
|
113
|
-
minify: opt.minify,
|
|
114
|
-
outfile: opt.bundle,
|
|
115
|
-
entryPoints: [opt.chunk],
|
|
116
|
-
entryNames: '[name]-[hash]'
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
const file = Object.keys(metafile.outputs)[0]
|
|
120
|
-
const data = {
|
|
121
|
-
file,
|
|
122
|
-
hash: file.match(/-(.*).js/)[1],
|
|
123
|
-
bundle: file.match(/.*(\/.*)$/)[1]
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Delete old bundle
|
|
128
|
-
*/
|
|
129
|
-
const manifest = resolve.main(opt.output, 'manifest.json')
|
|
130
|
-
if(exists(manifest)) {
|
|
131
|
-
const files = readdir(resolve.main(opt.output))
|
|
132
|
-
|
|
133
|
-
files.forEach((file) => {
|
|
134
|
-
const matched = file.name.match(/^bundle-([A-Z0-9+]{8})\.js$/)
|
|
135
|
-
if(matched) {
|
|
136
|
-
if(data.hash == matched[1]) {
|
|
137
|
-
return
|
|
138
|
-
}
|
|
139
|
-
unlink(path.resolve(file.parentPath, file.name))
|
|
140
|
-
}
|
|
141
|
-
})
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
await promises.writeFile(manifest, JSON.stringify(data), {flag: 'w'})
|
|
145
|
-
unlink(opt.chunk)
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
*
|
|
151
|
-
* @param {object} config
|
|
152
|
-
*/
|
|
153
|
-
module.exports = async function(opt) {
|
|
154
|
-
/**
|
|
155
|
-
* Create build directory
|
|
156
|
-
*/
|
|
157
|
-
await makedir(resolve.main(opt.output))
|
|
158
|
-
|
|
159
|
-
opt.entry = resolve.main(opt.entry)
|
|
160
|
-
opt.chunk = resolve.main(opt.output, 'chunk.jsx')
|
|
161
|
-
opt.bundle = resolve.main(opt.output, 'bundle.js')
|
|
162
|
-
opt.template = resolve.dirname(__filename, 'template.js')
|
|
163
|
-
opt.components = resolve.dirname(opt.entry, 'components')
|
|
164
|
-
|
|
165
|
-
try {
|
|
166
|
-
await build(opt)
|
|
167
|
-
await bundle(opt)
|
|
168
|
-
}
|
|
169
|
-
catch(e) {
|
|
170
|
-
console.log(e)
|
|
171
|
-
}
|
|
172
|
-
}
|
package/bin/template.js
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import React from 'react'
|
|
3
|
-
import client from '@vindo/react/client'
|
|
4
|
-
|
|
5
|
-
const chunk = {}
|
|
6
|
-
|
|
7
|
-
{CHUNK_IMPORTS}
|
|
8
|
-
{ENTRY_IMPORTS}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
if(chunk.head || chunk.body) {
|
|
12
|
-
throw new SyntaxError(`Component name '${chunk.head ? 'head' : 'body'}' is a reserved name for SSR client components.`)
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
chunk.head = function head(meta) {
|
|
16
|
-
const {props:{children}} = React.cloneElement((
|
|
17
|
-
{HEAD_COMPONENT}
|
|
18
|
-
))
|
|
19
|
-
return children
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
chunk.body = function body(props) {
|
|
23
|
-
const {props:{children}, type} = React.cloneElement((
|
|
24
|
-
{BODY_COMPONENT}
|
|
25
|
-
))
|
|
26
|
-
return type({children, ...props})
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
client.render(document, chunk)
|
|
File without changes
|