1 | const path = require('path')
|
2 |
|
3 | let config = require('./config')
|
4 | const utils = require('./utils')
|
5 | const parse = require('./parser/parse')
|
6 |
|
7 | const state = {
|
8 |
|
9 | mode: null,
|
10 | proc: null,
|
11 |
|
12 | config,
|
13 |
|
14 | entries: [],
|
15 | deps: [],
|
16 |
|
17 | rootPath: null,
|
18 | exclude: null,
|
19 | clientRoot: '.',
|
20 |
|
21 | }
|
22 |
|
23 | const cache = []
|
24 |
|
25 |
|
26 | async function init(mode, root, options) {
|
27 | state.mode = mode
|
28 | state.proc = config[mode]
|
29 |
|
30 | let userConfig = {}
|
31 | try {
|
32 | userConfig = require(path.resolve('webo.config.js'))
|
33 | merge(config, userConfig)
|
34 | } catch (error) { }
|
35 |
|
36 | if (root) config.srcRoot = root
|
37 | state.rootPath = path.join(path.resolve('.'), config.srcRoot)
|
38 | state.exclude = [...config.exclude, config.destRoot + '/**']
|
39 | await resolveLayout()
|
40 |
|
41 | merge(state, options)
|
42 | let discoveredEntries = await utils.globAsync('**/*.html', { cwd: '.', nodir: true, ignore: state.exclude })
|
43 |
|
44 | state.entries = [... new Set(discoveredEntries)]
|
45 |
|
46 | await Promise.all(state.entries.filter(o => !o.endsWith('_layout.html')).map(loadFile))
|
47 |
|
48 | }
|
49 |
|
50 |
|
51 |
|
52 | async function loadFile(filename, type) {
|
53 | if (filename.endsWith('.html')) {
|
54 | if (!state.entries.includes(filename)) state.entries.push(filename)
|
55 | }
|
56 | const { deps } = await parse(filename, state.mode, type)
|
57 |
|
58 | await Promise.all(
|
59 | deps.filter(dep => !state.deps.find(o => o.path === dep.path && o.ref === dep.ref)).map(async dep => {
|
60 | state.deps.push(dep)
|
61 | await loadFile(dep.path, dep.type)
|
62 | })
|
63 | )
|
64 | }
|
65 |
|
66 |
|
67 |
|
68 |
|
69 |
|
70 |
|
71 |
|
72 |
|
73 |
|
74 |
|
75 | async function removeEntry(filename) {
|
76 | state.entries.splice(state.entries.indexOf(filename), 1)
|
77 | state.deps = state.deps.filter(o => o.root !== filename)
|
78 | }
|
79 |
|
80 | function attachDep(dep) {
|
81 | const existingIndex = state.deps.findIndex(o => o.path === dep.path)
|
82 | existingIndex === -1 ? state.deps.push(dep) : state.deps.splice(existingIndex, 1, dep)
|
83 | }
|
84 |
|
85 |
|
86 |
|
87 |
|
88 |
|
89 |
|
90 |
|
91 | async function createDep(referrer, rel, type, root) {
|
92 |
|
93 | if (rel.startsWith('http://') || rel.startsWith('https://')) return null
|
94 | let depPath
|
95 | if (rel.startsWith('/')) {
|
96 | depPath = state.config.srcRoot + rel
|
97 | } else {
|
98 | depPath = path.relative('.' , path.resolve(path.dirname(referrer), rel.split('?')[0])).replace(/\\/g, '/')
|
99 | }
|
100 | let dep = {
|
101 | path: depPath,
|
102 | get pathHashed() {
|
103 | if (!this.hash) return this.rel
|
104 | const parts = this.path.split('.')
|
105 | if (parts.length > 1) {
|
106 | parts[parts.length - 2] += '-' + this.hash
|
107 | return parts.join('.')
|
108 | }
|
109 | },
|
110 | ref: referrer,
|
111 | rel,
|
112 | get relHashed() {
|
113 | if (!this.hash) return this.rel
|
114 | const relParts = this.rel.split('.')
|
115 | if (relParts.length > 1) {
|
116 | relParts[relParts.length - 2] += '-' + this.hash
|
117 |
|
118 | return relParts.join('.')
|
119 | }
|
120 | },
|
121 | type,
|
122 | root,
|
123 | }
|
124 | if (state.proc.hash) dep.hash = await utils.calcHash(depPath)
|
125 |
|
126 |
|
127 |
|
128 |
|
129 |
|
130 |
|
131 |
|
132 |
|
133 | return dep
|
134 | }
|
135 |
|
136 |
|
137 | function setDep(dep) {
|
138 | const index = state.deps.findIndex(o => o.ref === dep.ref && o.path === dep.path)
|
139 | if (index !== -1) {
|
140 | state.deps.slice(index, 1, dep)
|
141 | } else {
|
142 | state.deps.push(dep)
|
143 | }
|
144 | }
|
145 |
|
146 |
|
147 |
|
148 |
|
149 |
|
150 | function merge(dest = {}, src) {
|
151 | for (const prop in src) {
|
152 | if (!dest[prop] || !dest.hasOwnProperty(prop)) { dest[prop] = src[prop]; continue; }
|
153 | if (Array.isArray(dest[prop]) && Array.isArray(src[prop])) { dest[prop].push(... src[prop]); dest[prop] = [... new Set(dest[prop])]; continue; }
|
154 | if (typeof dest[prop] === 'object') { merge(dest[prop], src[prop]); continue; }
|
155 | dest[prop] = src[prop]
|
156 | }
|
157 | }
|
158 |
|
159 | async function resolveLayout() {
|
160 | let layoutPath = state.config.layout || (await utils.globAsync('**/_layout.html', { cwd: '.', nodir: true, ignore: state.exclude }))[0]
|
161 | if (!layoutPath) return
|
162 | try {
|
163 | await utils.readFileAsync(layoutPath)
|
164 | state.config.layout = layoutPath
|
165 | console.log(`using layout file ${state.config.layout}`)
|
166 | } catch (error) {
|
167 | if (layoutPath) console.error(`layout file not found ${layoutPath}`)
|
168 | if (state.config.layout) process.exit()
|
169 | }
|
170 |
|
171 |
|
172 |
|
173 |
|
174 |
|
175 |
|
176 |
|
177 |
|
178 |
|
179 |
|
180 |
|
181 |
|
182 |
|
183 |
|
184 |
|
185 |
|
186 | }
|
187 |
|
188 |
|
189 | module.exports = {
|
190 | state,
|
191 | cache,
|
192 | init,
|
193 | loadFile,
|
194 |
|
195 | removeEntry,
|
196 | setDep,
|
197 | createDep,
|
198 | }
|