poops 1.9.4 → 1.9.6
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/README.md +4 -4
- package/lib/copy.js +1 -1
- package/lib/scripts.js +40 -4
- package/lib/styles.js +33 -3
- package/lib/utils/helpers.js +69 -2
- package/package.json +1 -1
- package/poops.js +14 -12
package/README.md
CHANGED
|
@@ -216,8 +216,8 @@ You can freely remove the properties that you don't need. For example, if you do
|
|
|
216
216
|
|
|
217
217
|
Scripts are bundled with [esbuild](https://esbuild.github.io/). Supports `.js`, `.ts`, `.jsx`, and `.tsx` files out of the box — including React and other JSX frameworks. You can specify multiple scripts to bundle. Each script has the following properties:
|
|
218
218
|
|
|
219
|
-
- `in` - the input path, can be a file path, an array of file paths, or a glob pattern (e.g. `"src/js/*.js"`). Globs must use `/` separators (even on Windows)
|
|
220
|
-
- `out` - the output path, can be a directory or a file path. With multiple inputs it must be a directory — entry points from different directories nest their output under the common ancestor (esbuild's `outbase`)
|
|
219
|
+
- `in` - the input path, can be a file path, an array of file paths, or a glob pattern (e.g. `"src/js/*.js"`, `"src/elements/*/index.{js,mjs,cjs,jsx,ts,tsx}"`). Globs must use `/` separators (even on Windows)
|
|
220
|
+
- `out` - the output path, can be a directory or a file path. With multiple inputs it must be a directory — entry points from different directories nest their output under the common ancestor (esbuild's `outbase`). A **glob-matched** `index.*` is named after its directory instead, placed relative to the glob's static prefix: `"src/elements/*/index.js"` gives `<out>/accordion.js`, while `"src/*/accordion/index.js"` keeps the differing segment as `<out>/blocks/accordion.js`. A literal `in: "src/index.js"` keeps its own basename. To name outputs yourself, `out` can be a **template**: `{{dir}}` is the input's directory relative to the glob's static prefix, `{{name}}` its basename without extension. `"src/elements/*/widget.ts"` with `out: "dist/js/{{dir}}-{{name}}.js"` gives `dist/js/accordion-widget.js` and `dist/js/tabs-widget.js` — one bundle per match, named by you instead of by the common ancestor. The template's extension is honoured too, so `out: "dist/esm/{{dir}}.mjs"` writes `.mjs` files
|
|
221
221
|
- `options` - the options for the bundler. You can apply most of the esbuild options that are not in conflict with Poops. See [esbuild's options](https://esbuild.github.io/api/#build-api) for more info.
|
|
222
222
|
|
|
223
223
|
**Options:**
|
|
@@ -350,8 +350,8 @@ Poops does not need `react` or `react-dom` as its own dependency — they are re
|
|
|
350
350
|
|
|
351
351
|
Styles are bundled with [Dart Sass](https://sass-lang.com/dart-sass). You can specify multiple styles to bundle. Each style has the following properties:
|
|
352
352
|
|
|
353
|
-
- `in` - the input path, can be a file path, an array of file paths, or a glob pattern (e.g. `"src/scss/*.scss"`). Globs must use `/` separators (even on Windows) and skip Sass partials (`_*.scss`). Each matched file is compiled separately
|
|
354
|
-
- `out` - the output path, can be a directory or a file path. With multiple inputs it must be a directory — each input compiles to `<out>/<basename>.css`, so inputs sharing a basename (e.g. `a/main.scss` and `b/main.scss`) will overwrite each other
|
|
353
|
+
- `in` - the input path, can be a file path, an array of file paths, or a glob pattern (e.g. `"src/scss/*.scss"`, `"src/elements/*/index.{scss,sass,css}"`). Globs must use `/` separators (even on Windows) and skip Sass partials (`_*.scss`). Each matched file is compiled separately
|
|
354
|
+
- `out` - the output path, can be a directory or a file path. With multiple inputs it must be a directory — each input compiles to `<out>/<basename>.css`, so inputs sharing a basename (e.g. `a/main.scss` and `b/main.scss`) will overwrite each other. A **glob-matched** `index.*` is named after its directory instead, placed relative to the glob's static prefix: `"src/elements/*/index.scss"` gives `<out>/accordion.css`, while `"src/*/accordion/index.scss"` keeps the differing segment as `<out>/blocks/accordion.css`. A literal `in: "src/scss/index.scss"` keeps its own basename. To name outputs yourself, `out` can be a **template**: `{{dir}}` is the input's directory relative to the glob's static prefix, `{{name}}` its basename without extension. `"src/elements/*/theme.scss"` with `out: "dist/{{dir}}-theme.css"` gives `dist/accordion-theme.css` and `dist/tabs-theme.css` — one output per match, instead of every `theme.scss` overwriting the same `theme.css`
|
|
355
355
|
- `options` - the options for the bundler.
|
|
356
356
|
|
|
357
357
|
**Options:**
|
package/lib/copy.js
CHANGED
|
@@ -34,7 +34,7 @@ export default class Copy {
|
|
|
34
34
|
|
|
35
35
|
for (const inEntry of inEntries) {
|
|
36
36
|
const hasGlobMagic = typeof hasMagic === 'function'
|
|
37
|
-
? hasMagic(inEntry)
|
|
37
|
+
? hasMagic(inEntry, { magicalBraces: true })
|
|
38
38
|
: ['*', '?', '[', ']', '{', '}', '(', ')', '!'].some((ch) => inEntry.includes(ch))
|
|
39
39
|
let matches = []
|
|
40
40
|
try {
|
package/lib/scripts.js
CHANGED
|
@@ -3,12 +3,21 @@ import { globSync, hasMagic } from 'glob'
|
|
|
3
3
|
import { deepMerge } from 'book-of-spells'
|
|
4
4
|
import {
|
|
5
5
|
pathExists,
|
|
6
|
+
mkDir,
|
|
6
7
|
mkPath,
|
|
7
8
|
pathForFile,
|
|
9
|
+
isIndexEntry,
|
|
10
|
+
globBase,
|
|
11
|
+
indexEntryOut,
|
|
12
|
+
entryBase,
|
|
13
|
+
hasOutTemplate,
|
|
14
|
+
outTemplateBase,
|
|
15
|
+
fillOutTemplateEntry,
|
|
8
16
|
fillBannerTemplate,
|
|
9
17
|
buildTime,
|
|
10
18
|
fileSize
|
|
11
19
|
} from './utils/helpers.js'
|
|
20
|
+
import path from 'node:path'
|
|
12
21
|
import minifyToFile from './utils/minify.js'
|
|
13
22
|
import log from './utils/log.js'
|
|
14
23
|
|
|
@@ -27,26 +36,50 @@ export default class Scripts {
|
|
|
27
36
|
// `in` may be an array of entry points — pathExists on an array throws
|
|
28
37
|
const configured = Array.isArray(scriptEntry.in) ? scriptEntry.in : [scriptEntry.in]
|
|
29
38
|
const entryPoints = []
|
|
39
|
+
// A templated `out` names one output per entry point. esbuild already
|
|
40
|
+
// takes a per-entry `out` relative to `outdir` — the same door the
|
|
41
|
+
// `index.*` rename goes through — so the template resolves into that.
|
|
42
|
+
const templated = hasOutTemplate(scriptEntry.out)
|
|
43
|
+
const outBase = templated ? outTemplateBase(scriptEntry.out) : null
|
|
30
44
|
let missing = false
|
|
31
45
|
for (const entry of configured) {
|
|
32
|
-
if (hasMagic(entry)) {
|
|
46
|
+
if (hasMagic(entry, { magicalBraces: true })) {
|
|
33
47
|
// Globs must use `/` even on Windows; sort for deterministic build order
|
|
34
48
|
const matches = globSync(entry, { posix: true }).sort()
|
|
35
49
|
if (!matches.length) {
|
|
36
50
|
log({ tag: 'script', error: true, text: 'Entry does not exist:', link: entry })
|
|
37
51
|
missing = true
|
|
38
52
|
}
|
|
39
|
-
|
|
53
|
+
// A glob-matched `index.js` is named after its directory, relative to
|
|
54
|
+
// the glob's static prefix — otherwise every component collides on
|
|
55
|
+
// `index.js` or nests a level deep under esbuild's outbase. Literal
|
|
56
|
+
// entries keep their basename — see `isIndexEntry`.
|
|
57
|
+
const base = globBase(entry)
|
|
58
|
+
entryPoints.push(...matches.map(match => {
|
|
59
|
+
if (templated) return { in: match, out: fillOutTemplateEntry(scriptEntry.out, match, base, outBase) }
|
|
60
|
+
return isIndexEntry(match) ? { in: match, out: indexEntryOut(match, base) } : match
|
|
61
|
+
}))
|
|
40
62
|
} else if (!pathExists(entry)) {
|
|
41
63
|
log({ tag: 'script', error: true, text: 'Entry does not exist:', link: entry })
|
|
42
64
|
missing = true
|
|
65
|
+
} else if (templated) {
|
|
66
|
+
entryPoints.push({ in: entry, out: fillOutTemplateEntry(scriptEntry.out, entry, entryBase(entry), outBase) })
|
|
43
67
|
} else {
|
|
44
68
|
entryPoints.push(entry)
|
|
45
69
|
}
|
|
46
70
|
}
|
|
47
71
|
if (missing) continue
|
|
48
|
-
|
|
49
|
-
|
|
72
|
+
let options = scriptEntry.options
|
|
73
|
+
if (templated) {
|
|
74
|
+
mkDir(outBase)
|
|
75
|
+
// esbuild appends its own extension, so a template asking for one other
|
|
76
|
+
// than `.js` has to be honoured through `outExtension` or it's ignored
|
|
77
|
+
const ext = path.posix.extname(scriptEntry.out)
|
|
78
|
+
if (ext && ext !== '.js') options = { outExtension: { '.js': ext }, ...options }
|
|
79
|
+
} else {
|
|
80
|
+
mkPath(scriptEntry.out)
|
|
81
|
+
}
|
|
82
|
+
await this.compileEntry(entryPoints, templated ? outBase : scriptEntry.out, options)
|
|
50
83
|
}
|
|
51
84
|
}
|
|
52
85
|
|
|
@@ -78,6 +111,9 @@ export default class Scripts {
|
|
|
78
111
|
log({ tag: 'error', text: 'Cannot output multiple script files to a single file. Please specify an output directory path instead.' })
|
|
79
112
|
process.exit(1)
|
|
80
113
|
}
|
|
114
|
+
// An explicit `outfile` names the output itself, so drop any `{ in, out }`
|
|
115
|
+
// wrapper the glob resolver added — esbuild rejects the pair.
|
|
116
|
+
opts.entryPoints = infilePath.map(entry => entry.in || entry)
|
|
81
117
|
opts.outfile = outfilePath
|
|
82
118
|
}
|
|
83
119
|
|
package/lib/styles.js
CHANGED
|
@@ -5,6 +5,12 @@ import {
|
|
|
5
5
|
mkPath,
|
|
6
6
|
pathForFile,
|
|
7
7
|
buildStyleOutputFilePath,
|
|
8
|
+
isIndexEntry,
|
|
9
|
+
globBase,
|
|
10
|
+
indexEntryOut,
|
|
11
|
+
entryBase,
|
|
12
|
+
hasOutTemplate,
|
|
13
|
+
fillOutTemplate,
|
|
8
14
|
fillBannerTemplate,
|
|
9
15
|
buildTime,
|
|
10
16
|
fileSize
|
|
@@ -20,19 +26,27 @@ export default class Styles {
|
|
|
20
26
|
constructor(config) {
|
|
21
27
|
this.config = config
|
|
22
28
|
this.banner = config.banner ? fillBannerTemplate(config.banner) : null
|
|
29
|
+
// The css files the last compile wrote. Globs and templated `out` name a
|
|
30
|
+
// file per match, so the config alone can't say what landed where — the
|
|
31
|
+
// livereload chain reads this to hot-swap exactly the stylesheets rebuilt.
|
|
32
|
+
this.outputs = []
|
|
23
33
|
}
|
|
24
34
|
|
|
25
35
|
async compile() {
|
|
26
36
|
if (!this.config.styles) return
|
|
37
|
+
this.outputs = []
|
|
27
38
|
this.config.styles = Array.isArray(this.config.styles) ? this.config.styles : [this.config.styles]
|
|
28
39
|
for (const styleEntry of this.config.styles) {
|
|
29
40
|
if (!styleEntry.in || !styleEntry.out) continue
|
|
30
41
|
// `in` may be an array of entry points and/or globs — same resolution as scripts
|
|
31
42
|
const configured = Array.isArray(styleEntry.in) ? styleEntry.in : [styleEntry.in]
|
|
32
43
|
const entryPoints = []
|
|
44
|
+
const indexEntries = new Map()
|
|
45
|
+
const bases = new Map()
|
|
46
|
+
const templated = hasOutTemplate(styleEntry.out)
|
|
33
47
|
let missing = false
|
|
34
48
|
for (const entry of configured) {
|
|
35
|
-
if (hasMagic(entry)) {
|
|
49
|
+
if (hasMagic(entry, { magicalBraces: true })) {
|
|
36
50
|
// Globs must use `/` even on Windows; sort for deterministic build order.
|
|
37
51
|
// Skip sass partials (_*.scss) — they are imports, not entry points.
|
|
38
52
|
const matches = globSync(entry, { posix: true }).filter(match => !path.basename(match).startsWith('_')).sort()
|
|
@@ -40,21 +54,36 @@ export default class Styles {
|
|
|
40
54
|
log({ tag: 'style', error: true, text: 'Entry does not exist:', link: entry })
|
|
41
55
|
missing = true
|
|
42
56
|
}
|
|
57
|
+
// A glob-matched `index.scss` is named after its directory, relative
|
|
58
|
+
// to the glob's static prefix — otherwise every component collides on
|
|
59
|
+
// `index.css`. Literal entries keep their basename — see `isIndexEntry`.
|
|
60
|
+
const base = globBase(entry)
|
|
61
|
+
for (const match of matches) {
|
|
62
|
+
if (isIndexEntry(match)) indexEntries.set(match, indexEntryOut(match, base))
|
|
63
|
+
bases.set(match, base)
|
|
64
|
+
}
|
|
43
65
|
entryPoints.push(...matches)
|
|
44
66
|
} else if (!pathExists(entry)) {
|
|
45
67
|
log({ tag: 'style', error: true, text: 'Entry does not exist:', link: entry })
|
|
46
68
|
missing = true
|
|
47
69
|
} else {
|
|
70
|
+
bases.set(entry, entryBase(entry))
|
|
48
71
|
entryPoints.push(entry)
|
|
49
72
|
}
|
|
50
73
|
}
|
|
51
74
|
if (missing) continue
|
|
52
|
-
|
|
75
|
+
// A templated `out` resolves to a different file per entry point, so the
|
|
76
|
+
// single-file guard doesn't apply to it
|
|
77
|
+
if (!templated && entryPoints.length > 1 && pathForFile(styleEntry.out)) {
|
|
53
78
|
log({ tag: 'error', text: 'Cannot output multiple style files to a single file. Please specify an output directory path instead.' })
|
|
54
79
|
process.exit(1)
|
|
55
80
|
}
|
|
56
81
|
for (const entryPoint of entryPoints) {
|
|
57
|
-
|
|
82
|
+
// Resolve the index rename here, where `out` is still known to be a dir
|
|
83
|
+
let out = styleEntry.out
|
|
84
|
+
if (templated) out = fillOutTemplate(out, entryPoint, bases.get(entryPoint))
|
|
85
|
+
else if (indexEntries.has(entryPoint) && !pathForFile(out)) out = path.join(out, `${indexEntries.get(entryPoint)}.css`)
|
|
86
|
+
await this.compileEntry(entryPoint, out, styleEntry.options)
|
|
58
87
|
}
|
|
59
88
|
}
|
|
60
89
|
}
|
|
@@ -98,6 +127,7 @@ export default class Styles {
|
|
|
98
127
|
const mapsrc = options.sourcemap ? `\n/*# sourceMappingURL=${path.basename(outfilePath)}.map */` : ''
|
|
99
128
|
if (this.banner) compiledSass.css = this.banner + '\n' + compiledSass.css
|
|
100
129
|
fs.writeFileSync(outfilePath, compiledSass.css + mapsrc)
|
|
130
|
+
this.outputs.push(outfilePath) // after the write — a failed compile has nothing to reload
|
|
101
131
|
const stylesEnd = performance.now()
|
|
102
132
|
if (!options.justMinified) log({ tag: 'style', text: 'Compiled:', link: outfilePath, size: fileSize(outfilePath), time: buildTime(stylesStart, stylesEnd) })
|
|
103
133
|
|
package/lib/utils/helpers.js
CHANGED
|
@@ -34,10 +34,77 @@ export function insertMinSuffix(filePath) {
|
|
|
34
34
|
return path.join(path.dirname(filePath), `${name}.min${ext}`)
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
// `index` is a directory entry point — name the output after the directory, so
|
|
38
|
+
// `src/elements/accordion/index.scss` outputs `accordion.css`. Only applied to
|
|
39
|
+
// glob matches: a literal `in: 'src/index.js'` keeps its own basename, since
|
|
40
|
+
// renaming it to `src.js` would silently move an explicitly named output.
|
|
41
|
+
export function isIndexEntry(inputPath) {
|
|
42
|
+
const { dir, name } = path.parse(inputPath)
|
|
43
|
+
return name === 'index' && !!dir && dir !== '.' && dir !== '..'
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// The static prefix of a glob — everything before its first magic segment.
|
|
47
|
+
// `src/elements/*/index.ts` → `src/elements`, `src/*/accordion/index.ts` → `src`.
|
|
48
|
+
// Derived from the pattern alone, so it doesn't shift with what happened to match.
|
|
49
|
+
export function globBase(pattern) {
|
|
50
|
+
const parts = toPosix(pattern).split('/')
|
|
51
|
+
const magicAt = parts.findIndex(part => hasMagic(part, { magicalBraces: true }))
|
|
52
|
+
return parts.slice(0, magicAt === -1 ? -1 : magicAt).join('/')
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Extension-less output path for a glob-matched `index.*`: the directory name,
|
|
56
|
+
// keeping whatever nesting the glob's static prefix doesn't already account for.
|
|
57
|
+
// A glob spanning several groups (`src/*/accordion/index.ts`) therefore stays
|
|
58
|
+
// apart as `blocks/accordion` and `elements/accordion` instead of colliding.
|
|
59
|
+
export function indexEntryOut(inputPath, base) {
|
|
60
|
+
const dir = path.posix.dirname(toPosix(inputPath))
|
|
61
|
+
const rel = base ? path.posix.relative(base, dir) : dir
|
|
62
|
+
return rel || path.posix.basename(dir)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// The `index.*` rename only saves entry points named `index` — every other
|
|
66
|
+
// glob match still collapses onto its own basename, so `src/elements/*/theme.scss`
|
|
67
|
+
// writes `theme.css` once per element and the last one wins. An `out` carrying
|
|
68
|
+
// `{{dir}}`/`{{name}}` names one output per match instead.
|
|
69
|
+
export function hasOutTemplate(outputPath) {
|
|
70
|
+
return /{{\s*(dir|name)\s*}}/.test(outputPath)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// `{{dir}}` is the match's directory relative to the glob's static prefix (the
|
|
74
|
+
// same name `index.*` entries get), `{{name}}` its extension-less basename.
|
|
75
|
+
export function fillOutTemplate(template, inputPath, base) {
|
|
76
|
+
const { name } = path.parse(inputPath)
|
|
77
|
+
return template
|
|
78
|
+
.replace(/{{\s*dir\s*}}/g, indexEntryOut(inputPath, base))
|
|
79
|
+
.replace(/{{\s*name\s*}}/g, name)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// What an entry's outputs are named relative to: a glob's static prefix, or a
|
|
83
|
+
// literal entry's own directory (so `{{dir}}` is that directory's name).
|
|
84
|
+
export function entryBase(entry) {
|
|
85
|
+
return hasMagic(entry, { magicalBraces: true }) ? globBase(entry) : path.posix.dirname(toPosix(entry))
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// The fixed directory a templated `out` writes into — everything before its
|
|
89
|
+
// first token. Only the tail varies per entry point, so this is the one part
|
|
90
|
+
// esbuild (and the watcher's output zones) can be handed up front.
|
|
91
|
+
export function outTemplateBase(outputPath) {
|
|
92
|
+
return path.posix.dirname(toPosix(outputPath).replace(/\{\{.*$/, ''))
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// esbuild names an entry point's output itself, from an `out` that is relative
|
|
96
|
+
// to `outdir` and carries no extension. Resolve the `out` template into that
|
|
97
|
+
// shape so a templated scripts `out` goes through the same machinery the
|
|
98
|
+
// `index.*` rename already uses.
|
|
99
|
+
export function fillOutTemplateEntry(template, inputPath, base, outBase) {
|
|
100
|
+
const filled = toPosix(fillOutTemplate(template, inputPath, base))
|
|
101
|
+
return path.posix.relative(outBase, filled).replace(/\.[^./]*$/, '')
|
|
102
|
+
}
|
|
103
|
+
|
|
37
104
|
export function buildStyleOutputFilePath(inputPath, outputPath) {
|
|
38
105
|
if (pathForFile(outputPath)) return outputPath
|
|
39
106
|
const { name } = path.parse(inputPath)
|
|
40
|
-
return path.join(
|
|
107
|
+
return path.join(outputPath, `${name}.css`)
|
|
41
108
|
}
|
|
42
109
|
|
|
43
110
|
export function fillBannerTemplate(template, packagesPath) {
|
|
@@ -185,7 +252,7 @@ export function pathContainsPathSegment(filePath, segment) {
|
|
|
185
252
|
// Watcher paths arrive with native separators, config segments with `/`
|
|
186
253
|
filePath = toPosix(filePath)
|
|
187
254
|
segment = stripDirNavSegments(segment)
|
|
188
|
-
if (hasMagic(segment)) {
|
|
255
|
+
if (hasMagic(segment, { magicalBraces: true })) {
|
|
189
256
|
segment = convertGlobToRegex(segment)
|
|
190
257
|
if (!segment) return false
|
|
191
258
|
return segment.test(filePath)
|
package/package.json
CHANGED
package/poops.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import chokidar from 'chokidar'
|
|
4
4
|
import Copy from './lib/copy.js'
|
|
5
5
|
import runExec, { validateExec } from './lib/exec.js'
|
|
6
|
-
import { pathExists, doesFileBelongToPath, pathContainsPathSegment, deriveWatchDirs, toPosix } from './lib/utils/helpers.js'
|
|
6
|
+
import { pathExists, doesFileBelongToPath, pathContainsPathSegment, deriveWatchDirs, toPosix, hasOutTemplate, outTemplateBase } from './lib/utils/helpers.js'
|
|
7
7
|
import http from 'node:http'
|
|
8
8
|
import net from 'node:net'
|
|
9
9
|
import os from 'node:os'
|
|
@@ -89,17 +89,15 @@ function reload(file) {
|
|
|
89
89
|
}, 500)
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
// The css
|
|
93
|
-
// to reload() so style edits hot-swap.
|
|
94
|
-
//
|
|
92
|
+
// The css files the last styles compile actually wrote — what the styles chain
|
|
93
|
+
// reports to reload() so style edits hot-swap. Read off the compiler rather
|
|
94
|
+
// than guessed from the config: a glob or a templated `out` names one output
|
|
95
|
+
// per match, and a guessed path the browser has no stylesheet for silently
|
|
96
|
+
// downgrades the hot-swap to a full page reload.
|
|
95
97
|
// toPosix: the livereload client matches these against URL paths, so Windows
|
|
96
98
|
// backslashes would silently break the CSS hot-swap.
|
|
97
|
-
function styleOutputs(
|
|
98
|
-
return
|
|
99
|
-
.filter((entry) => entry && entry.in && entry.out)
|
|
100
|
-
.map((entry) => toPosix(path.extname(entry.out)
|
|
101
|
-
? entry.out
|
|
102
|
-
: path.join(entry.out, path.basename(entry.in).replace(/\.(sass|scss)$/i, '.css'))))
|
|
99
|
+
function styleOutputs(styles) {
|
|
100
|
+
return styles.outputs.map(toPosix)
|
|
103
101
|
}
|
|
104
102
|
|
|
105
103
|
// Per-stage shell hooks (config.exec). Runs after a stage compiles in both
|
|
@@ -135,7 +133,11 @@ function setupWatchers(config, modules) {
|
|
|
135
133
|
// the watch list, or every compile retriggers itself.
|
|
136
134
|
const outputZones = [config.scripts, config.styles].flat()
|
|
137
135
|
.filter((entry) => entry && entry.out)
|
|
138
|
-
|
|
136
|
+
// A templated `out` is only static up to its first token, so zone on that
|
|
137
|
+
// prefix — `dist/{{dir}}/theme.css` writes into `dist`, not `dist/{{dir}}`
|
|
138
|
+
.map((entry) => (hasOutTemplate(entry.out)
|
|
139
|
+
? outTemplateBase(entry.out)
|
|
140
|
+
: (path.extname(entry.out) ? path.dirname(entry.out) : entry.out)))
|
|
139
141
|
.filter((zone) => zone && zone !== '.')
|
|
140
142
|
const isBuildOutput = (file) => outputZones.some((zone) => pathContainsPathSegment(file, zone))
|
|
141
143
|
|
|
@@ -161,7 +163,7 @@ function setupWatchers(config, modules) {
|
|
|
161
163
|
|
|
162
164
|
const recompileStyles = coalesce(() => {
|
|
163
165
|
modules.styles.compile().then(() => modules.postcss.compile())
|
|
164
|
-
.then(() => { hook('styles'); styleOutputs(
|
|
166
|
+
.then(() => { hook('styles'); styleOutputs(modules.styles).forEach((out) => reload(out)) })
|
|
165
167
|
.catch(err => console.error(err))
|
|
166
168
|
})
|
|
167
169
|
|