poops 1.0.1 → 1.0.2

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/poops.js CHANGED
@@ -1,20 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const autoprefixer = require('autoprefixer')
4
- const { build } = require('esbuild')
5
3
  const chokidar = require('chokidar')
6
4
  const connect = require('connect')
7
- const cssnano = require('cssnano')
8
- const deepmerge = require('deepmerge')
9
- const fs = require('node:fs')
5
+ const helpers = require('./lib/utils/helpers.js')
10
6
  const http = require('node:http')
11
7
  const livereload = require('livereload')
8
+ const Markups = require('./lib/markups.js')
12
9
  const path = require('node:path')
13
- const { pathToFileURL } = require('node:url')
14
- const postcss = require('postcss')
15
- const sass = require('sass')
16
10
  const serveStatic = require('serve-static')
17
- const Terser = require('terser')
11
+ const Scripts = require('./lib/scripts.js')
12
+ const PrintStyle = require('./lib/utils/print-style.js')
13
+ const Styles = require('./lib/styles.js')
14
+
15
+ const { pathExists } = helpers
18
16
 
19
17
  const cwd = process.cwd() // Current Working Directory
20
18
  const pkg = require('./package.json')
@@ -26,161 +24,73 @@ if (args.length) {
26
24
  defaultConfigPath = args[0]
27
25
  }
28
26
 
29
- // Helpers
27
+ const configPath = path.join(cwd, defaultConfigPath)
28
+ // Load poops.json
29
+ const config = require(configPath)
30
30
 
31
- class Style {
32
- reset = '\x1b[0m'
33
- bold = '\x1b[1m'
34
- dim = '\x1b[2m'
35
- italic = '\x1b[3m'
36
- underline = '\x1b[4m'
37
- blink = '\x1b[5m'
38
- inverse = '\x1b[7m'
39
- hidden = '\x1b[8m'
40
- strikethrough = '\x1b[9m'
41
- black = '\x1b[30m'
42
- red = '\x1b[31m'
43
- redBright = '\x1b[91m'
44
- green = '\x1b[32m'
45
- greenBright = '\x1b[92m'
46
- yellow = '\x1b[33m'
47
- yellowBright = '\x1b[93m'
48
- blue = '\x1b[34m'
49
- blueBright = '\x1b[94m'
50
- magenta = '\x1b[35m'
51
- magentaBright = '\x1b[95m'
52
- cyan = '\x1b[36m'
53
- cyanBright = '\x1b[96m'
54
- white = '\x1b[37m'
55
- whiteBright = '\x1b[97m'
56
- gray = '\x1b[90m'
57
- bgBlack = '\x1b[40m'
58
- bgRed = '\x1b[41m'
59
- bgRedBright = '\x1b[101m'
60
- bgGreen = '\x1b[42m'
61
- bgGreenBright = '\x1b[102m'
62
- bgYellow = '\x1b[43m'
63
- bgYellowBright = '\x1b[103m'
64
- bgBlue = '\x1b[44m'
65
- bgBlueBright = '\x1b[104m'
66
- bgMagenta = '\x1b[45m'
67
- bgMagentaBright = '\x1b[105m'
68
- bgCyan = '\x1b[46m'
69
- bgCyanBright = '\x1b[106m'
70
- bgWhite = '\x1b[47m'
71
- bgWhiteBright = '\x1b[107m'
72
- bgGray = '\x1b[100m'
73
- bell = '\x07'
31
+ const pstyle = new PrintStyle()
74
32
 
75
- hexToRgb(hex) {
76
- const sanitizedHex = hex.replace('#', '')
77
- const red = parseInt(sanitizedHex.substring(0, 2), 16)
78
- const green = parseInt(sanitizedHex.substring(2, 4), 16)
79
- const blue = parseInt(sanitizedHex.substring(4, 6), 16)
33
+ // JS/TS Compiler
80
34
 
81
- return [red, green, blue]
82
- }
35
+ // Main function 💩
36
+ function poops() {
37
+ const styles = new Styles(config)
38
+ const scripts = new Scripts(config)
39
+ const markups = new Markups(config)
83
40
 
84
- terminalColorIndex(red, green, blue) {
85
- return 16 +
86
- Math.round(red / 255 * 5) * 36 +
87
- Math.round(green / 255 * 5) * 6 +
88
- Math.round(blue / 255 * 5)
89
- }
41
+ if (config.livereload) {
42
+ const lrExcludes = ['.git', '.svn', '.hg']
90
43
 
91
- color(hex) {
92
- const [red, green, blue] = this.hexToRgb(hex)
44
+ if (config.watch) {
45
+ lrExcludes.push(...config.watch)
46
+ }
93
47
 
94
- return `\x1b[38;5;${this.terminalColorIndex(red, green, blue)}m`
95
- }
48
+ if (config.includePaths) {
49
+ lrExcludes.push(...config.includePaths)
50
+ }
96
51
 
97
- background(hex) {
98
- const [red, green, blue] = this.hexToRgb(hex)
52
+ if (config.livereload.exclude) {
53
+ lrExcludes.push(...config.livereload.exclude)
54
+ }
99
55
 
100
- return `\x1b[48;5;${this.terminalColorIndex(red, green, blue)}m`
56
+ const lrserver = livereload.createServer({
57
+ exclusions: [...new Set(lrExcludes)],
58
+ port: config.livereload.port || 35729
59
+ })
60
+ console.log(`${pstyle.blue + pstyle.bold}[info]${pstyle.reset} ${pstyle.dim}🔃 LiveReload server:${pstyle.reset} ${pstyle.italic + pstyle.underline}http://localhost:${lrserver.config.port}${pstyle.reset}`)
61
+ lrserver.watch(cwd)
101
62
  }
102
- }
103
-
104
- function pathExists() {
105
- return fs.existsSync(path.join(...arguments))
106
- }
107
63
 
108
- function pathIsDirectory() {
109
- return fs.lstatSync(path.join(...arguments)).isDirectory()
110
- }
111
-
112
- function mkPath(filePath) {
113
- const dirPath = path.dirname(filePath)
114
- if (!fs.existsSync(dirPath)) fs.mkdirSync(dirPath, { recursive: true })
115
- }
116
-
117
- function pathForFile(filePath) {
118
- return /\./.test(path.basename(filePath))
119
- }
120
-
121
- function insertMinSuffix(filePath) {
122
- const { name, ext } = path.parse(filePath)
123
- return path.join(path.dirname(filePath), `${name}.min${ext}`)
124
- }
125
-
126
- function buildStyleOutputFilePath(inputPath, outputPath) {
127
- if (pathForFile(outputPath)) return outputPath
128
- const { name } = path.parse(inputPath)
129
- return path.join(path.join(outputPath, `${name}.css`))
130
- }
64
+ styles.compile()
65
+ scripts.compile()
66
+ markups.compile()
131
67
 
132
- function buildScriptOutputFilePath(inputPath, outputPath) {
133
- if (pathForFile(outputPath)) return outputPath
134
- const { name, ext } = path.parse(inputPath)
135
- return path.join(path.join(outputPath, `${name}${ext.replace('t', 'j')}`))
136
- }
137
-
138
- function fillBannerTemplate(template, packagesPath) {
139
- packagesPath = packagesPath || cwd
140
- const packagesFilePath = path.join(packagesPath, 'package.json')
141
- if (!pathExists(packagesFilePath)) return template
142
- const pkg = require(packagesFilePath)
143
- const { name, version, homepage, description, license, author } = pkg
144
- const year = new Date().getFullYear()
145
-
146
- return template
147
- .replace(/{{\s?name\s?}}/g, name)
148
- .replace(/{{\s?version\s?}}/g, version)
149
- .replace(/{{\s?homepage\s?}}/g, homepage)
150
- .replace(/{{\s?description\s?}}/g, description)
151
- .replace(/{{\s?author\s?}}/g, author)
152
- .replace(/{{\s?license\s?}}/g, license)
153
- .replace(/{{\s?year\s?}}/g, year)
154
- }
68
+ if (config.watch) {
69
+ chokidar.watch(config.watch).on('change', (file) => {
70
+ if (/(\.js|\.ts)$/i.test(file)) scripts.compile()
71
+ if (/(\.sass|\.scss|\.css)$/i.test(file)) styles.compile()
72
+ if (/(\.html|\.njk)$/i.test(file)) markups.compile()
73
+ })
74
+ }
155
75
 
156
- function buildTime(start, end) {
157
- const time = Math.round(end - start)
158
- if (time < 1000) return `${time}ms`
159
- if (time < 60 * 1000) return `${(time / 1000).toFixed(0)}s ${time % 1000}ms`
160
- return `${(time / 1000 / 60).toFixed(0)}m ${((time / 1000) % 60).toFixed(0)}s ${time % 1000}ms`
76
+ if (!config.watch && !config.livereload && !config.serve) {
77
+ process.exit(1)
78
+ }
161
79
  }
162
80
 
163
- const style = new Style()
164
-
165
81
  // CLI Header
166
- console.log(`\n${style.color('#8b4513')}💩 Poops — v${pkg.version}
167
- ----------------${style.reset + style.bell}\n`)
168
-
169
- const configPath = path.join(cwd, defaultConfigPath)
82
+ console.log(`\n${pstyle.color('#8b4513')}💩 Poops — v${pkg.version}
83
+ ----------------${pstyle.reset + pstyle.bell}\n`)
170
84
 
171
85
  // Check if poops.json exists
172
86
  if (!pathExists(configPath)) {
173
- console.log(`${style.redBright + style.bold}[error]${style.reset} \`${style.underline}${defaultConfigPath}${style.reset}\` not found.
174
- ${style.dim}Configuration file \`${style.underline}${defaultConfigPath}${style.reset}${style.dim}\` not found in your working directory: ${style.underline}${cwd}${style.reset}\n
175
- ${style.dim}Please specify another file path or create a \`poops.json\` file in your working directory and try again.\n
176
- ${style.dim}For information on the structure of the configuration file, please visit: \n${style.underline}https://stamat.github.io/poops${style.reset}\n`)
87
+ console.log(`${pstyle.redBright + pstyle.bold}[error]${pstyle.reset} \`${pstyle.underline}${defaultConfigPath}${pstyle.reset}\` not found.
88
+ ${pstyle.dim}Configuration file \`${pstyle.underline}${defaultConfigPath}${pstyle.reset}${pstyle.dim}\` not found in your working directory: ${pstyle.underline}${cwd}${pstyle.reset}\n
89
+ ${pstyle.dim}Please specify another file path or create a \`poops.json\` file in your working directory and try again.\n
90
+ ${pstyle.dim}For information on the structure of the configuration file, please visit: \n${pstyle.underline}https://stamat.github.io/poops${pstyle.reset}\n`)
177
91
  process.exit(1)
178
92
  }
179
93
 
180
- // Load poops.json
181
- const config = require(configPath)
182
- const banner = config.banner ? fillBannerTemplate(config.banner) : null
183
-
184
94
  if (config.watch) {
185
95
  config.watch = Array.isArray(config.watch) ? config.watch : [config.watch]
186
96
  }
@@ -203,250 +113,9 @@ if (config.serve) {
203
113
 
204
114
  const port = config.serve.port ? parseInt(config.serve.port, 10) : 4040
205
115
  http.createServer(app).listen(port, () => {
206
- console.log(`${style.cyanBright + style.bold}[info]${style.reset} ${style.dim}🌍 Local server:${style.reset} ${style.italic + style.underline}http://localhost:${port}${style.reset}`)
116
+ console.log(`${pstyle.blue + pstyle.bold}[info]${pstyle.reset} ${pstyle.dim}🌍 Local server:${pstyle.reset} ${pstyle.italic + pstyle.underline}http://localhost:${port}${pstyle.reset}`)
207
117
  poops()
208
118
  })
209
119
  } else {
210
120
  poops()
211
121
  }
212
-
213
- // SCSS/SASS Compiler
214
- function tryToFindFile(filePath, extensions) {
215
- const fileExt = extensions.find(ext => fs.existsSync(`${filePath}.${ext}`))
216
- if (fileExt) {
217
- return `${filePath}.${fileExt}`
218
- }
219
- return null
220
- }
221
-
222
- function sassPathResolver(url, resolvePath) {
223
- if (fs.existsSync(url)) return new URL(url)
224
- const resolvedPath = pathToFileURL(resolvePath)
225
- if (!fs.existsSync(resolvedPath.pathname)) return null
226
- const importPath = path.relative(cwd, path.join(resolvedPath.pathname, url))
227
-
228
- if (!fs.existsSync(importPath)) {
229
- const correctFile = tryToFindFile(importPath, ['sass', 'scss', 'css'])
230
- if (correctFile) return new URL(correctFile, resolvedPath)
231
- }
232
-
233
- if (pathIsDirectory(importPath) && !pathExists(importPath, 'index.sass') && !pathExists(importPath, 'index.scss')) {
234
- const correctFile = tryToFindFile(importPath, ['sass', 'scss', 'css'])
235
- if (correctFile) return new URL(correctFile, resolvedPath)
236
-
237
- if (!pathExists(importPath, 'package.json')) return null
238
-
239
- const pkg = require(path.join(importPath, 'package.json'))
240
-
241
- if (pkg.sass) return new URL(path.join(importPath, pkg.sass), resolvedPath)
242
- if (pkg.css) return new URL(path.join(importPath, pkg.css), resolvedPath)
243
-
244
- const basename = path.basename(pkg.main)
245
- if (pkg.main && /(\.sass|\.scss|\.css)$/i.test(basename)) return new URL(path.join(importPath, pkg.main), resolvedPath)
246
- return null
247
- }
248
-
249
- return new URL(importPath, resolvedPath)
250
- }
251
-
252
- function compileStyles() {
253
- if (!config.styles) return
254
- config.styles = Array.isArray(config.styles) ? config.styles : [config.styles]
255
- for (const styleEntry of config.styles) {
256
- if (styleEntry.in && styleEntry.out && pathExists(styleEntry.in)) {
257
- mkPath(styleEntry.out)
258
- compileStylesEntry(styleEntry.in, styleEntry.out, styleEntry.options)
259
- }
260
- }
261
- }
262
-
263
- function compileStylesEntry(infilePath, outfilePath, options = {}) {
264
- const opts = {
265
- sourceMap: false,
266
- sourceMapIncludeSources: false,
267
- importers: [{
268
- // Resolve `includePaths`.
269
- findFileUrl(url) {
270
- for (const includePath of config.includePaths) {
271
- const resolvedPath = sassPathResolver(url, includePath)
272
- if (resolvedPath) return resolvedPath
273
- }
274
- return null
275
- }
276
- }]
277
- }
278
-
279
- if (options.sourcemap) {
280
- opts.sourceMap = options.sourcemap
281
- opts.sourceMapIncludeSources = options.sourcemap
282
- }
283
-
284
- outfilePath = buildStyleOutputFilePath(infilePath, outfilePath, options)
285
-
286
- const cssStart = performance.now()
287
- const compiledSass = sass.compile(infilePath, opts)
288
- const mapsrc = options.sourcemap ? `\n/*# sourceMappingURL=${path.basename(outfilePath)}.map */` : ''
289
- if (banner) compiledSass.css = banner + '\n' + compiledSass.css
290
- fs.writeFileSync(outfilePath, compiledSass.css + mapsrc)
291
- const cssEnd = performance.now()
292
- if (!options.justMinified) console.log(`${style.magentaBright + style.bold}[style]${style.reset} ${style.dim}Compiled:${style.reset} ${style.italic + style.underline}${outfilePath}${style.reset} ${style.greenBright}(${buildTime(cssStart, cssEnd)})${style.reset}`)
293
-
294
- if (compiledSass.sourceMap) {
295
- if (banner) compiledSass.sourceMap.mappings = ';' + compiledSass.sourceMap.mappings
296
- fs.writeFileSync(`${outfilePath}.map`, JSON.stringify(compiledSass.sourceMap))
297
- console.log(`${style.magentaBright + style.bold}[style]${style.reset} ${style.dim}Compiled:${style.reset} ${style.italic + style.underline}${outfilePath}.map${style.reset}`)
298
- }
299
-
300
- const cssMinStart = performance.now()
301
- const minPath = insertMinSuffix(outfilePath)
302
- if (options.minify) {
303
- postcss([autoprefixer, cssnano]).process(compiledSass.css, {
304
- from: outfilePath,
305
- to: minPath
306
- }).then(result => {
307
- if (banner) result.css = banner + '\n' + result.css
308
- fs.writeFileSync(minPath, result.css)
309
- const cssMinEnd = performance.now()
310
- console.log(`${style.magentaBright + style.bold}[style]${style.reset} ${style.dim}Compiled:${style.reset} ${style.italic + style.underline}${minPath}${style.reset} ${style.greenBright}(${buildTime(cssMinStart, cssMinEnd)})${style.reset}`)
311
- }).catch((error) => {
312
- console.log(`${style.redBright + style.bold}[error]${style.reset} Error occurred during CSS minification: ${style.dim}${error}${style.reset}`)
313
- })
314
-
315
- if (options.justMinified) {
316
- fs.unlinkSync(outfilePath)
317
- }
318
- } else {
319
- fs.unlinkSync(minPath)
320
- }
321
- }
322
-
323
- // JS/TS Compiler
324
- function compileScripts() {
325
- if (!config.scripts) return
326
- config.scripts = Array.isArray(config.scripts) ? config.scripts : [config.scripts]
327
- for (const scriptEntry of config.scripts) {
328
- if (scriptEntry.in && scriptEntry.out && pathExists(scriptEntry.in)) {
329
- mkPath(scriptEntry.out)
330
- compileScriptsEntry(scriptEntry.in, scriptEntry.out, scriptEntry.options)
331
- }
332
- }
333
- }
334
-
335
- function compileScriptsEntry(infilePath, outfilePath, options = {}) {
336
- if (!Array.isArray(infilePath)) infilePath = [infilePath]
337
-
338
- const opts = {
339
- logLevel: 'error',
340
- entryPoints: infilePath,
341
- bundle: true,
342
- sourcemap: false,
343
- minify: false,
344
- format: 'iife',
345
- target: 'es2019',
346
- nodePaths: config.includePaths // Resolve `includePaths`
347
- }
348
-
349
- const terserOpts = {
350
- mangle: false
351
- }
352
-
353
- if (banner) {
354
- opts.banner = {
355
- js: banner,
356
- css: banner
357
- }
358
- }
359
-
360
- if (!pathForFile(outfilePath)) {
361
- opts.outdir = outfilePath
362
- } else {
363
- if (infilePath.length > 1) {
364
- console.log(`${style.redBright + style.bold}[error]${style.reset} Cannot output multiple ${style.bold + style.underline}script${style.reset} files to a single file. Please specify an output directory path instead.`)
365
- process.exit(1)
366
- }
367
- opts.outfile = outfilePath
368
- }
369
-
370
- if (options.format) opts.format = options.format
371
- if (options.target) opts.target = options.target
372
- if (options.nodePaths) opts.nodePaths = [...new Set([...opts.nodePaths, ...options.nodePaths])]
373
- if (options.sourcemap) opts.sourcemap = options.sourcemap
374
-
375
- if (options.mangle) terserOpts.mangle = options.mangle
376
-
377
- delete options.mangle
378
- deepmerge(opts, options) // ability to pass other esbuild options `node_modules/esbuild/lib/main.d.ts`
379
-
380
- // TODO: Actually loop the build process for each entry point!!!
381
- const esbuildStart = performance.now()
382
- build(opts).then(() => {
383
- const esbuildEnd = performance.now()
384
- for (const entry of infilePath) {
385
- const newOutFilePath = buildScriptOutputFilePath(entry, outfilePath)
386
- const minPath = insertMinSuffix(newOutFilePath)
387
-
388
- if (!options.justMinified) console.log(`${style.yellowBright + style.bold}[script]${style.reset} ${style.dim}Compiled:${style.reset} ${style.italic + style.underline}${newOutFilePath}${style.reset} ${style.greenBright}(${buildTime(esbuildStart, esbuildEnd)})${style.reset}`)
389
- if (options.sourcemap) console.log(`${style.yellowBright + style.bold}[script]${style.reset} ${style.dim}Compiled:${style.reset} ${style.italic + style.underline}${newOutFilePath}.map${style.reset}`)
390
-
391
- if (options.minify) {
392
- const terserStart = performance.now()
393
- Terser.minify(fs.readFileSync(newOutFilePath, 'utf-8'), { mangle: terserOpts.mangle }).then((result) => {
394
- if (result.error) {
395
- console.log(`${style.redBright + style.bold}[error]${style.reset} Error occurred during JS minification: ${style.dim}${result.error}${style.reset}`)
396
- } else {
397
- if (banner) result.code = banner + '\n' + result.code
398
- fs.writeFileSync(minPath, result.code)
399
- const terserEnd = performance.now()
400
- console.log(`${style.yellowBright + style.bold}[script]${style.reset} ${style.dim}Compiled:${style.reset} ${style.italic + style.underline}${minPath}${style.reset} ${style.greenBright}(${buildTime(terserStart, terserEnd)})${style.reset}`)
401
- }
402
- })
403
-
404
- if (options.justMinified) {
405
- fs.unlinkSync(newOutFilePath)
406
- }
407
- } else {
408
- fs.unlinkSync(minPath)
409
- }
410
- }
411
- })
412
- }
413
-
414
- // Main function 💩
415
- function poops() {
416
- if (config.livereload) {
417
- const lrExcludes = ['.git', '.svn', '.hg']
418
-
419
- if (config.watch) {
420
- lrExcludes.push(...config.watch)
421
- }
422
-
423
- if (config.includePaths) {
424
- lrExcludes.push(...config.includePaths)
425
- }
426
-
427
- if (config.livereload.exclude) {
428
- lrExcludes.push(...config.livereload.exclude)
429
- }
430
-
431
- const lrserver = livereload.createServer({
432
- exclusions: [...new Set(lrExcludes)],
433
- port: config.livereload.port || 35729
434
- })
435
- console.log(`${style.cyanBright + style.bold}[info]${style.reset} ${style.dim}🔃 LiveReload server:${style.reset} ${style.italic + style.underline}http://localhost:${lrserver.config.port}${style.reset}`)
436
- lrserver.watch(cwd)
437
- }
438
-
439
- compileStyles()
440
- compileScripts()
441
-
442
- if (config.watch) {
443
- chokidar.watch(config.watch).on('change', (file) => {
444
- if (/(\.js|\.ts)$/i.test(file)) compileScripts()
445
- if (/(\.sass|\.scss|\.css)$/i.test(file)) compileStyles()
446
- })
447
- }
448
-
449
- if (!config.watch && !config.livereload && !config.serve) {
450
- process.exit(1)
451
- }
452
- }
package/poops.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "scripts": [{
3
- "in": "src/js/main.ts",
4
- "out": "dist/js/scripts.js",
3
+ "in": "example/src/js/main.ts",
4
+ "out": "example/dist/js/scripts.js",
5
5
  "options": {
6
6
  "sourcemap": true,
7
7
  "minify": true,
@@ -12,14 +12,26 @@
12
12
  }
13
13
  }],
14
14
  "styles": [{
15
- "in": "src/scss/index.scss",
16
- "out": "dist/css/styles.css",
15
+ "in": "example/src/scss/index.scss",
16
+ "out": "example/dist/css/styles.css",
17
17
  "options": {
18
18
  "sourcemap": true,
19
19
  "minify": true,
20
20
  "justMinified": false
21
21
  }
22
22
  }],
23
+ "markup": {
24
+ "in": "example/src/markup",
25
+ "out": "/",
26
+ "site": {
27
+ "title": "💩 Poops",
28
+ "description": "Straightforward, no-bullshit bundler for the web."
29
+ },
30
+ "includePaths": [
31
+ "_layouts",
32
+ "_partials"
33
+ ]
34
+ },
23
35
  "banner": "/* {{ name }} v{{ version }} | {{ homepage }} | {{ license }} License */",
24
36
  "serve" : {
25
37
  "port": 4040,
@@ -27,7 +39,7 @@
27
39
  },
28
40
  "livereload": true,
29
41
  "watch": [
30
- "src"
42
+ "example/src"
31
43
  ],
32
44
  "includePaths": [
33
45
  "node_modules"