simplest-sitegen
Advanced tools
Comparing version 0.6.0 to 0.7.0
@@ -0,1 +1,17 @@ | ||
/// <reference types="node" resolution-mode="require"/> | ||
declare type TemplatePlugin = (context: Context, template: string) => Promise<string>; | ||
interface Rename { | ||
file: string; | ||
content: string | Buffer; | ||
} | ||
interface Remove { | ||
file: 'remove'; | ||
} | ||
interface Keep { | ||
file: 'keep'; | ||
} | ||
declare type FilesPlugin = { | ||
extensions: string[]; | ||
parse: (context: Context, file: string) => Promise<string | Buffer | Rename | Remove | Keep>; | ||
}; | ||
declare type HtmlParser = { | ||
@@ -5,12 +21,17 @@ template: string; | ||
}; | ||
declare const config: { | ||
declare const defaultConfig: { | ||
input: string; | ||
output: string; | ||
template: string; | ||
ignoreExtensions: string[]; | ||
templatePlugins: TemplatePlugin[]; | ||
filesPlugin: FilesPlugin[]; | ||
}; | ||
declare const context: { | ||
config: typeof config; | ||
export declare type Config = typeof defaultConfig; | ||
export declare type Context = { | ||
config: Config; | ||
parser: HtmlParser; | ||
}; | ||
export declare type Context = typeof context; | ||
export declare const simplest: (config?: Config) => Promise<void>; | ||
export declare const simplestWatch: (config?: Config) => Promise<void>; | ||
export {}; |
@@ -15,79 +15,15 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
import path from 'upath'; | ||
import minimist from 'minimist'; | ||
import sane from 'sane'; | ||
import { cacheBust, htmlFiles } from './plugins.js'; | ||
import { cacheBust, compileSass, htmlFiles } from './plugins.js'; | ||
import { cwd } from 'process'; | ||
const config = { | ||
const defaultConfig = { | ||
input: "src", | ||
output: "build", | ||
template: "src/template.html" | ||
template: "src/template.html", | ||
ignoreExtensions: [".sass", ".scss", ".less"], | ||
templatePlugins: [], | ||
filesPlugin: [] | ||
}; | ||
const context = { | ||
config, | ||
parser: null | ||
}; | ||
const d = debug('simplest'); | ||
const outputFile = (file) => path.join(config.output, file.slice(config.input.length)); | ||
const isNewer = (src, dest) => Promise.all([fs.stat(src), fs.stat(dest)]) | ||
.then(([src1, dest1]) => { | ||
const output = src1.mtimeMs > dest1.mtimeMs; | ||
return output; | ||
}) | ||
.catch(e => { | ||
return true; | ||
}); | ||
const parseTemplate = (plugins) => __awaiter(void 0, void 0, void 0, function* () { | ||
const { output, template } = config; | ||
const templateOutputFile = path.join(output, path.basename(template)); | ||
const templateChanged = yield isNewer(template, templateOutputFile); | ||
let templateContent = context.parser.template; | ||
for (const plugin of plugins) { | ||
templateContent = yield plugin(context, templateContent); | ||
} | ||
context.parser.template = templateContent; | ||
if (templateChanged) { | ||
fs.outputFile(templateOutputFile, templateContent); | ||
return true; | ||
} | ||
const currentTemplate = yield fs.readFile(templateOutputFile, { encoding: 'utf8' }); | ||
if (currentTemplate != templateContent) { | ||
fs.outputFile(templateOutputFile, templateContent); | ||
return true; | ||
} | ||
return false; | ||
}); | ||
const parseFiles = ((plugins, templateChanged) => __awaiter(void 0, void 0, void 0, function* () { | ||
if (templateChanged) | ||
d('Template content changed, unconditional update.'); | ||
const allFiles = yield fg(path.join(config.input, `/**/*.*`)); | ||
const parseFiles = (exts, parse) => __awaiter(void 0, void 0, void 0, function* () { | ||
const files = allFiles.filter((f) => exts.some((ext) => f.endsWith('.' + ext))); | ||
let queue = files; | ||
if (!templateChanged) { | ||
const newer = yield Promise.all(files.map((file) => isNewer(file, outputFile(file)))); | ||
queue = files.filter((file, i) => newer[i]); | ||
} | ||
return queue.map((f) => ({ | ||
file: f, | ||
content: parse(context, f) | ||
})); | ||
}); | ||
for (const plugin of plugins) { | ||
const files = yield parseFiles(plugin.extensions, plugin.parse); | ||
const results = yield Promise.allSettled(files.map(f => f.content)); | ||
for (const [i, result] of results.entries()) { | ||
if (result.status != 'fulfilled') | ||
continue; | ||
fs.outputFile(outputFile(files[i].file), result.value); | ||
allFiles.splice(allFiles.indexOf(files[i].file), 1); | ||
} | ||
} | ||
for (const file of allFiles) { | ||
const output = outputFile(file); | ||
if (!(yield isNewer(file, output))) | ||
continue; | ||
fs.outputFile(output, yield fs.readFile(file)); | ||
} | ||
})); | ||
const start = () => __awaiter(void 0, void 0, void 0, function* () { | ||
const start = (config2, watch = false) => __awaiter(void 0, void 0, void 0, function* () { | ||
const config = Object.assign({}, defaultConfig); | ||
try { | ||
@@ -98,2 +34,4 @@ const userConfig = yield import('file:///' + path.join(cwd(), 'simplest.config.js')); | ||
catch (e) { } | ||
if (config2) | ||
Object.assign(config, config2); | ||
try { | ||
@@ -109,17 +47,86 @@ yield fs.access(config.template); | ||
}); | ||
Object.assign(context, { | ||
const context = { | ||
config, | ||
parser | ||
}; | ||
const outputFile = (file) => path.join(config.output, file.slice(config.input.length)); | ||
const isNewer = (src, dest) => Promise.all([fs.stat(src), fs.stat(dest)]) | ||
.then(([src1, dest1]) => { | ||
const output = src1.mtimeMs > dest1.mtimeMs; | ||
return output; | ||
}) | ||
.catch(e => { | ||
return true; | ||
}); | ||
const args = minimist(process.argv.slice(2)); | ||
const watch = args._.includes('watch'); | ||
const template = () => parseTemplate([cacheBust]); | ||
const files = (templateChanged) => parseFiles([htmlFiles], templateChanged); | ||
const hasExtension = (exts) => (input) => exts.some(ext => input.endsWith(`${ext}`)); | ||
const hasntExtension = (exts) => (input) => !exts.some(ext => input.endsWith(`${ext}`)); | ||
const parseTemplate = (plugins) => __awaiter(void 0, void 0, void 0, function* () { | ||
const { output, template } = config; | ||
const templateOutputFile = path.join(output, path.basename(template)); | ||
const templateChanged = yield isNewer(template, templateOutputFile); | ||
let templateContent = context.parser.template; | ||
for (const plugin of plugins) { | ||
templateContent = yield plugin(context, templateContent); | ||
} | ||
context.parser.template = templateContent; | ||
if (templateChanged) { | ||
fs.outputFile(templateOutputFile, templateContent); | ||
return true; | ||
} | ||
const currentTemplate = yield fs.readFile(templateOutputFile, { encoding: 'utf8' }); | ||
if (currentTemplate != templateContent) { | ||
fs.outputFile(templateOutputFile, templateContent); | ||
return true; | ||
} | ||
return false; | ||
}); | ||
const parseFiles = ((plugins, templateChanged) => __awaiter(void 0, void 0, void 0, function* () { | ||
const allFiles = new Set(yield fg(path.join(config.input, `/**/*.*`))); | ||
const parseFiles = (exts, parse) => __awaiter(void 0, void 0, void 0, function* () { | ||
const files = Array.from(allFiles).filter(hasExtension(exts)); | ||
let queue = files; | ||
if (!templateChanged) { | ||
const newer = yield Promise.all(files.map((file) => isNewer(file, outputFile(file)))); | ||
queue = files.filter((file, i) => newer[i]); | ||
} | ||
return queue.map((f) => ({ | ||
file: f, | ||
content: parse(context, f) | ||
})); | ||
}); | ||
for (const plugin of plugins) { | ||
const files = yield parseFiles(plugin.extensions, plugin.parse); | ||
for (const parsed of files) { | ||
const content = yield parsed.content; | ||
const removeFile = () => allFiles.delete(parsed.file); | ||
if (typeof content === 'string' || Buffer.isBuffer(content)) { | ||
fs.outputFile(outputFile(parsed.file), content); | ||
removeFile(); | ||
} | ||
else if (content.file == 'remove') { | ||
removeFile(); | ||
} | ||
else if (content.file == 'keep') { | ||
} | ||
else { | ||
const c = content; | ||
fs.outputFile(outputFile(c.file), c.content); | ||
removeFile(); | ||
} | ||
} | ||
} | ||
for (const file of [...allFiles].filter(hasntExtension(config.ignoreExtensions))) { | ||
const output = outputFile(file); | ||
if (!(yield isNewer(file, output))) | ||
continue; | ||
fs.outputFile(output, yield fs.readFile(file)); | ||
} | ||
})); | ||
const run = () => __awaiter(void 0, void 0, void 0, function* () { | ||
const templateChanged = yield template(); | ||
return files(templateChanged); | ||
const templateChanged = yield parseTemplate(config.templatePlugins.concat([compileSass, cacheBust])); | ||
return parseFiles(config.filesPlugin.concat([htmlFiles]), templateChanged); | ||
}); | ||
const runWatch = (filepath, root, stat) => { | ||
const relativePath = path.relative(path.join(config.input, '..'), path.join(root, filepath)); | ||
console.log('Update: ' + relativePath); | ||
console.log('Updated: ' + relativePath); | ||
return run(); | ||
@@ -132,9 +139,11 @@ }; | ||
console.log('Watching for file changes in "' + config.input + '"'); | ||
const watcher = sane(config.input); | ||
const sane = yield import('sane'); | ||
const watcher = sane.default(config.input); | ||
watcher.on('change', runWatch); | ||
watcher.on('add', runWatch); | ||
} | ||
run(); | ||
return run(); | ||
}); | ||
start(); | ||
export const simplest = (config) => start(config); | ||
export const simplestWatch = (config) => start(config, true); | ||
//# sourceMappingURL=index.js.map |
/// <reference types="node" resolution-mode="require"/> | ||
import { type Context } from './index.js'; | ||
export declare const cacheBust: (context: Context, template: string) => Promise<string>; | ||
export declare const compileSass: (context: Context, template: string) => Promise<string>; | ||
export declare const htmlFiles: { | ||
@@ -5,0 +6,0 @@ extensions: string[]; |
@@ -14,9 +14,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
import { hash } from './utils.js'; | ||
export const cacheBust = (context, template) => __awaiter(void 0, void 0, void 0, function* () { | ||
const root = parse(template, { comment: true }); | ||
const includeScript = (el, attr) => !!el.attributes[attr] && | ||
!el.attributes[attr].includes('?') && | ||
!el.attributes[attr].includes('//'); | ||
const scripts = (selector, attr) => root.querySelectorAll(selector) | ||
.filter((el) => includeScript(el, attr)) | ||
const isAbsolute = (url) => /^(?:[a-z+]+:)?\/\//i.test(url); | ||
const cssScripts = (root) => srcScripts(root, 'link', 'href'); | ||
const jsScripts = (root) => srcScripts(root, 'script', 'src'); | ||
const srcScripts = (root, selector, attr) => { | ||
return root.querySelectorAll(selector) | ||
.filter((el) => !!el.attributes[attr]) | ||
.map((el) => ({ | ||
@@ -27,18 +26,43 @@ el, | ||
})); | ||
const cssFiles = scripts('link', 'href'); | ||
const jsFiles = scripts('script', 'src'); | ||
const scriptFiles = jsFiles.concat(cssFiles); | ||
const content = yield Promise.allSettled(scriptFiles | ||
.map(script => path.join(context.config.input, script.file)) | ||
.map(file => fs.readFile(file))); | ||
content.forEach((res, i) => { | ||
if (res.status != 'fulfilled') | ||
return; | ||
const { el, attr, file } = scriptFiles[i]; | ||
el.setAttribute(attr, file + '?' + hash(res.value)); | ||
}); | ||
}; | ||
export const cacheBust = (context, template) => __awaiter(void 0, void 0, void 0, function* () { | ||
const root = parse(template, { comment: true }); | ||
const scriptFiles = cssScripts(root).concat(jsScripts(root)) | ||
.filter(f => !isAbsolute(f.file)); | ||
for (const { el, attr, file } of scriptFiles) { | ||
const inputPath = path.join(context.config.input, file); | ||
try { | ||
const content = yield fs.readFile(inputPath).catch(() => { | ||
const outputPath = path.join(context.config.output, file); | ||
return fs.readFile(outputPath); | ||
}); | ||
el.setAttribute(attr, file + '?' + hash(content)); | ||
} | ||
catch (e) { | ||
console.log('Warning: ' + file + ' not found, referenced in ' + context.config.template); | ||
} | ||
} | ||
return root.toString(); | ||
}); | ||
let sass; | ||
export const compileSass = (context, template) => __awaiter(void 0, void 0, void 0, function* () { | ||
var _a; | ||
const root = parse(template, { comment: true }); | ||
for (const link of cssScripts(root)) { | ||
if (!(link.file.endsWith('.sass') || link.file.endsWith('.scss'))) | ||
continue; | ||
if (!sass) | ||
sass = (yield import('sass')).default.compile; | ||
const compiled = sass(path.join(context.config.input, link.file)); | ||
const cssFileName = path.changeExt(link.file, '.css'); | ||
yield fs.outputFile(path.join(context.config.output, cssFileName), compiled.css); | ||
link.el.setAttribute(link.attr, cssFileName); | ||
if ((_a = compiled.sourceMap) === null || _a === void 0 ? void 0 : _a.file) { | ||
yield fs.outputFile(path.join(context.config.output, path.changeExt(link.file, '.css.map', undefined, 8)), compiled.sourceMap.file); | ||
} | ||
} | ||
return root.toString(); | ||
}); | ||
export const htmlFiles = { | ||
extensions: ['html', 'htm'], | ||
extensions: ['.html', '.htm'], | ||
parse: (context, file) => __awaiter(void 0, void 0, void 0, function* () { | ||
@@ -45,0 +69,0 @@ const content = yield fs.readFile(file); |
{ | ||
"name": "simplest-sitegen", | ||
"version": "0.6.0", | ||
"version": "0.7.0", | ||
"author": "", | ||
@@ -10,11 +10,13 @@ "description": "The simplest static sitegen there is. Build a modern, fully working website with only HTML.", | ||
"bin": { | ||
"simplest": "dist/index.js" | ||
"simplest": "dist/cli.js" | ||
}, | ||
"type": "module", | ||
"exports": "./dist/index.js", | ||
"scripts": { | ||
"build": "tsc", | ||
"dev": "nodemon dist/index.js", | ||
"dev": "nodemon -e \"js,css,html,sass\" -w dist -w expected -x \"npm run test\"", | ||
"server": "http-server -c-1 build", | ||
"prepublishOnly": "tsc", | ||
"start": "node dist/index.js", | ||
"test": "echo \"Error: no test specified\" && exit 1", | ||
"start": "node dist/cli.js", | ||
"test": "node dist/test.js", | ||
"watch": "tsc -w" | ||
@@ -35,3 +37,5 @@ }, | ||
"devDependencies": { | ||
"@npmcli/disparity-colors": "^2.0.0", | ||
"@types/debug": "^4.1.7", | ||
"@types/diff": "^5.0.2", | ||
"@types/fs-extra": "^9.0.13", | ||
@@ -41,4 +45,12 @@ "@types/minimist": "^1.2.2", | ||
"@types/sane": "^2.0.1", | ||
"nodemon": "^2.0.19" | ||
"ansi-colors": "^4.1.3", | ||
"diff": "^5.1.0", | ||
"dir-compare": "^4.0.0", | ||
"http-server": "^14.1.1", | ||
"nodemon": "^2.0.19", | ||
"sass": "^1.54.5", | ||
"ts-node": "^10.9.1", | ||
"typescript": "^4.7.4", | ||
"url": "^0.11.0" | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No tests
QualityPackage does not have any tests. This is a strong signal of a poorly maintained or low quality package.
Found 1 instance in 1 package
17
16
2
24760
16
308
1