@tenjuu99/blog 0.1.0

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.
Files changed (44) hide show
  1. package/.env.prod.sample +8 -0
  2. package/.env.sample +8 -0
  3. package/.github/workflows/deploy.yml.sample +51 -0
  4. package/.github/workflows/github-page.yml +47 -0
  5. package/.gitignore +9 -0
  6. package/README.md +145 -0
  7. package/generate.js +11 -0
  8. package/helper/add.js +3 -0
  9. package/helper/index.js +53 -0
  10. package/lib/applyTemplate.js +41 -0
  11. package/lib/cssGenerator.js +61 -0
  12. package/lib/dir.js +15 -0
  13. package/lib/distribute.js +35 -0
  14. package/lib/filter.js +167 -0
  15. package/lib/includeFilter.js +38 -0
  16. package/lib/indexer.js +82 -0
  17. package/lib/minify.js +22 -0
  18. package/lib/replaceIfFilter.js +67 -0
  19. package/lib/replaceScriptFilter.js +71 -0
  20. package/package.json +40 -0
  21. package/performance.js +22 -0
  22. package/server.js +48 -0
  23. package/src-sample/css/color.css +5 -0
  24. package/src-sample/css/layout.css +143 -0
  25. package/src-sample/css/markdown.css +49 -0
  26. package/src-sample/css/page.css +0 -0
  27. package/src-sample/css/reset.css +2 -0
  28. package/src-sample/helper/index.js +3 -0
  29. package/src-sample/image/.gitkeep +0 -0
  30. package/src-sample/pages/abc.md +5 -0
  31. package/src-sample/pages/fuga.md +3 -0
  32. package/src-sample/pages/hogehoge.md +8 -0
  33. package/src-sample/pages/html_render.html +20 -0
  34. package/src-sample/pages/index.md +35 -0
  35. package/src-sample/pages/notfound.md +8 -0
  36. package/src-sample/pages/rss.md +8 -0
  37. package/src-sample/pages/sample.md +145 -0
  38. package/src-sample/template/css.html +6 -0
  39. package/src-sample/template/default.html +55 -0
  40. package/src-sample/template/footer.html +7 -0
  41. package/src-sample/template/gtag.html +9 -0
  42. package/src-sample/template/index.html +24 -0
  43. package/src-sample/template/prevNext.html +35 -0
  44. package/src-sample/template/rss.xml +41 -0
@@ -0,0 +1,8 @@
1
+ SITE_NAME=test_blog
2
+ GTAG_ID=
3
+ URL_BASE=https://amashigeseiji.github.io/blog_template
4
+ RELATIVE_PATH=/blog_template
5
+ SRC_DIR=src-sample
6
+ DIST_DIR=dist
7
+ DISTRIBUTE_RAW=image
8
+ HELPER=helper/index.js
package/.env.sample ADDED
@@ -0,0 +1,8 @@
1
+ SITE_NAME=test_blog
2
+ GTAG_ID=
3
+ URL_BASE=http://localhost:8000
4
+ RELATIVE_PATH=
5
+ SRC_DIR=src-sample
6
+ DIST_DIR=dist
7
+ DISTRIBUTE_RAW=image
8
+ HELPER=helper/index.js
@@ -0,0 +1,51 @@
1
+ name: Build html and deploy to S3
2
+ on:
3
+ push:
4
+ branches:
5
+ - main
6
+ jobs:
7
+ build:
8
+ runs-on: ubuntu-latest
9
+ steps:
10
+ - name: Checkout
11
+ uses: actions/checkout@master
12
+
13
+ - name: npm install
14
+ run: npm install --production
15
+
16
+ - name: Build
17
+ run: npm run generate:prod
18
+
19
+ - name: Deploy
20
+ env:
21
+ AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
22
+ AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
23
+ run: |
24
+ aws s3 sync \
25
+ --region ap-northeast-1 \
26
+ --exact-timestamps \
27
+ --exclude .gitignore \
28
+ --exclude '*.css' \
29
+ --exclude 'image/' \
30
+ --delete \
31
+ --metadata-directive REPLACE \
32
+ --cache-control "max-age=86400, no-cache, public" \
33
+ dist/ s3://${{ secrets.S3_URL }}/
34
+
35
+ aws s3 sync \
36
+ --region ap-northeast-1 \
37
+ --exact-timestamps \
38
+ --exclude .gitignore \
39
+ --delete \
40
+ --metadata-directive REPLACE \
41
+ --cache-control "max-age=31536000, public" \
42
+ dist/css/ s3://${{ secrets.S3_URL }}/css/
43
+
44
+ aws s3 sync \
45
+ --region ap-northeast-1 \
46
+ --exact-timestamps \
47
+ --exclude .gitignore \
48
+ --delete \
49
+ --metadata-directive REPLACE \
50
+ --cache-control "max-age=86400, public" \
51
+ dist/image/ s3://${{ secrets.S3_URL }}/image/
@@ -0,0 +1,47 @@
1
+ # Simple workflow for deploying static content to GitHub Pages
2
+ name: Deploy static content to Pages
3
+
4
+ on:
5
+ push:
6
+ branches:
7
+ - main
8
+ workflow_dispatch:
9
+
10
+ # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
11
+ permissions:
12
+ contents: read
13
+ pages: write
14
+ id-token: write
15
+
16
+ # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
17
+ # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
18
+ concurrency:
19
+ group: "pages"
20
+ cancel-in-progress: false
21
+
22
+ jobs:
23
+ deploy:
24
+ environment:
25
+ name: github-pages
26
+ url: ${{ steps.deployment.outputs.page_url }}
27
+ runs-on: ubuntu-latest
28
+ steps:
29
+ - name: Checkout
30
+ uses: actions/checkout@master
31
+
32
+ - name: npm install
33
+ run: npm install --production
34
+
35
+ - name: Build
36
+ run: cp .env.prod.sample .env && npm run generate
37
+
38
+ - name: Setup Pages
39
+ uses: actions/configure-pages@v4
40
+ - name: Upload artifact
41
+ uses: actions/upload-pages-artifact@v3
42
+ with:
43
+ # Upload dist
44
+ path: './dist/'
45
+ - name: Deploy to GitHub Pages
46
+ id: deployment
47
+ uses: actions/deploy-pages@v4
package/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ node_modules/
2
+ dist/*
3
+ !.gitignore
4
+ !favicon.ico
5
+ data/index.json
6
+ .DS_Store
7
+
8
+ .env
9
+ .cache/
package/README.md ADDED
@@ -0,0 +1,145 @@
1
+ # blog template
2
+
3
+ ## プレビュー
4
+
5
+ 以下のコマンドでプレビューできます。
6
+
7
+ ```
8
+ npm run watch
9
+ ```
10
+
11
+ `http://localhost:8000/`
12
+
13
+ デフォルトでは8000ポートを利用しますが、環境変数を使ってポート番号を上書きできます。
14
+
15
+ ```
16
+ PORT=8001 npm run watch
17
+ ```
18
+
19
+ ## 記事を書く
20
+
21
+ `./data/` 以下にマークダウンファイルを入稿します。
22
+
23
+ ファイル冒頭を `---` でくくると変数を指定できます。
24
+ `title` と `url` と `published`が必須です。
25
+ `url` は出力先になります。
26
+ `published` は index ファイルがソートするのに利用します。
27
+
28
+ 次のような内容で、 `data/sample_article.md` などに保存すると `http://localhost:8000/sample_article` でアクセスできるようになります。
29
+
30
+ ```markdown
31
+ ---
32
+ title: sample article
33
+ url: /sample_article
34
+ published: 2024/03/18 00:00
35
+ ---
36
+
37
+ トップレベル見出しは `title` 変数が利用されます。
38
+ 変えたい場合は template/default.html を修正してください。
39
+
40
+ ## サンプル記事
41
+
42
+ これはサンプル記事です。ブログ開始のときに削除してください。
43
+
44
+ ## マークダウン
45
+
46
+ 記事は、マークダウン記法で記述し、 `.md` ファイルで保存してください。
47
+
48
+ リンクは次のように記述します。
49
+ [アンカーテキストはこちら](http://example.com)
50
+
51
+ リストは次のように記述します。
52
+ * リスト1
53
+ * リスト2
54
+ * リスト3
55
+
56
+ 画像は、 `data/image/` 以下に配置して、パスを指定してください。
57
+
58
+ `data/image/sample.jpg` などがあれば、次の指定になります。
59
+ `![代替テキストを入力してください](/image/sample.jpg)`
60
+
61
+ ![placeholdの画像](https://placehold.jp/150x150.png)
62
+
63
+ ### 見出し3
64
+
65
+ スクリプトを記述することができます。
66
+
67
+ {script}
68
+ return (new Date()).toString()
69
+ {/script}
70
+ ```
71
+
72
+ [data/sample.md](data/sample.md) にサンプルを置いています。
73
+
74
+ ## テンプレートエンジン
75
+
76
+ ### 変数
77
+
78
+ コメントに変数を記述できます。
79
+
80
+ ```markdown
81
+ ---
82
+ foo: fooVariableContent
83
+ bar: varVariableContent
84
+ ---
85
+ ```
86
+
87
+ and in template
88
+
89
+ ```html
90
+ <!DOCTYPE html>
91
+ <html lang="en">
92
+ <head>
93
+ <meta charset="UTF-8">
94
+ <title></title>
95
+ </head>
96
+ <body>
97
+ {{foo}}
98
+ {{bar}}
99
+ </body>
100
+ </html>
101
+ ```
102
+
103
+ `{{foo}}` と `{{bar}}` は `'fooVariableContent'` と `'barVariableContent'` に置換されます。
104
+
105
+ ### IF
106
+
107
+ ```markdown
108
+ ---
109
+ someVariable: true
110
+ ---
111
+ {if someVariable}
112
+ This content will be present.
113
+ {/if}
114
+
115
+ {if undefinedValue}
116
+ This content will be removed.
117
+ {else}
118
+ This is else content.
119
+ {/if}
120
+ ```
121
+
122
+ ### SCRIPT
123
+
124
+ ```markdown
125
+ {script}
126
+ // write javascript and return value
127
+ {/script}
128
+ ```
129
+
130
+ ## デプロイ
131
+
132
+ AWS S3 にデプロイするには、 `.github/workflow/deploy.yml.sample` を `.github/workflow/deploy.yml` にコピーします。
133
+ また、 `.env.prod` を作成して、以下の値を登録します。
134
+
135
+ ```
136
+ SITE_NAME: テストサイト
137
+ URL_BASE: https://example.com
138
+ GTAG_ID: G-xxxx
139
+ ```
140
+
141
+ GitHub のリポジトリで、`settings > Secrets and variables > Actions` から、以下の変数を登録します。
142
+
143
+ * `AWS_ACCESS_KEY_ID`
144
+ * `AWS_SECRET_ACCESS_KEY_ID`
145
+ * `S3_URL`
package/generate.js ADDED
@@ -0,0 +1,11 @@
1
+ "use strict"
2
+ import distribute from './lib/distribute.js'
3
+ import { indexing } from './lib/indexer.js'
4
+ import { srcDir, distDir } from './lib/dir.js'
5
+
6
+ const start = performance.now()
7
+ const data = await indexing(srcDir + '/pages/')
8
+
9
+ await distribute(data, srcDir, distDir)
10
+ const end = performance.now()
11
+ console.log('build: ' + (end - start) + "ms")
package/helper/add.js ADDED
@@ -0,0 +1,3 @@
1
+ export function hogehoge() {
2
+ return 'hogehoge'
3
+ }
@@ -0,0 +1,53 @@
1
+ import { allData } from "../lib/indexer.js";
2
+ import { replaceVariablesFilter } from "../lib/filter.js";
3
+
4
+ export function readIndex (filter = null) {
5
+ const data = Object.entries(allData)
6
+ .sort((a, b) => new Date(b[1].published) - new Date(a[1].published))
7
+ return filter
8
+ ? data.filter(v => v[0].indexOf(filter) === 0).map(v => v[1])
9
+ : data.map(v => v[1])
10
+ }
11
+
12
+ export function dateFormat(dateString) {
13
+ const date = new Date(dateString)
14
+ return `${date.getFullYear()}年${date.getMonth() + 1}月${date.getDate()}日`
15
+ }
16
+
17
+ export function render(text, variables) {
18
+ return replaceVariablesFilter(text, variables)
19
+ }
20
+
21
+ export function getPageData(name) {
22
+ return allData[name]
23
+ }
24
+
25
+ let indexedItemsSorted = null
26
+ export function indexedItems() {
27
+ if (indexedItemsSorted) {
28
+ return indexedItemsSorted
29
+ }
30
+ const sorted = readIndex()
31
+ .filter(v => v.index && v.published != '1970-01-01')
32
+ .sort((a, b) => new Date(a.published) - new Date(b.published))
33
+ let prev, next
34
+ for (const item of sorted) {
35
+ if (prev) {
36
+ prev.next = {
37
+ name: item.name,
38
+ published: item.published,
39
+ url: item.url,
40
+ title: item.title,
41
+ }
42
+ item.prev = {
43
+ name: prev.name,
44
+ published: prev.published,
45
+ url: prev.url,
46
+ title: prev.title,
47
+ }
48
+ }
49
+ prev = item
50
+ }
51
+ indexedItemsSorted = sorted
52
+ return indexedItemsSorted
53
+ }
@@ -0,0 +1,41 @@
1
+ "use strict"
2
+ import fs from "node:fs/promises";
3
+ import applyCss from './cssGenerator.js'
4
+ import {
5
+ replaceIfFilter,
6
+ replaceScriptFilter,
7
+ replaceVariablesFilter,
8
+ includeFilter
9
+ } from './filter.js'
10
+ import { marked } from "marked";
11
+ import { templateDir } from './dir.js'
12
+
13
+ const templates = {}
14
+
15
+ const applyTemplate = async (name = 'default.html') => {
16
+ if (templates[name]) {
17
+ return templates[name]
18
+ }
19
+ let templateContent = await fs.readFile(`${templateDir}/${name}`, 'utf8')
20
+ templateContent = await includeFilter(templateContent)
21
+ templateContent = await applyCss(templateContent)
22
+ templates[name] = templateContent
23
+ return templateContent
24
+ }
25
+
26
+ const render = async (templateName, data) => {
27
+ let template = await applyTemplate(templateName)
28
+ template = replaceIfFilter(template, data)
29
+ template = await replaceScriptFilter(template, data)
30
+
31
+ let markdown = data.markdown
32
+ markdown = await includeFilter(markdown)
33
+ markdown = await replaceIfFilter(markdown, data)
34
+ markdown = await replaceScriptFilter(markdown, data)
35
+ data.markdown = data.__filetype === 'md' ? marked.parse(markdown) : markdown
36
+
37
+
38
+ return replaceVariablesFilter(template, data)
39
+ }
40
+
41
+ export default render
@@ -0,0 +1,61 @@
1
+ "use strict"
2
+ import fs from "node:fs/promises";
3
+ import { minifyCss } from './minify.js'
4
+ import { createHash } from 'crypto'
5
+ import path from 'path'
6
+ import { distDir as distRoot, cssDir } from './dir.js'
7
+
8
+ const cacheBuster = {}
9
+ const cacheBusterQuery = 't'
10
+
11
+ /**
12
+ * @param {string} src
13
+ * @param {string} dist
14
+ * @returns {Promise<string>}
15
+ */
16
+ const cssGenerator = async (src, dist) => {
17
+ const key = `${dist}${src}`
18
+ // 生成済みの場合はそのまま返す
19
+ for (const a in cacheBuster) {
20
+ if (a === key) {
21
+ return cacheBuster[key]
22
+ }
23
+ }
24
+ let css = ''
25
+ for (const cssFile of src.split(',')) {
26
+ css += await fs.readFile(`${cssDir}/${cssFile}`)
27
+ }
28
+ css = minifyCss(css)
29
+ cacheBuster[key] = createHash('md5').update(css).digest('hex')
30
+
31
+ return await fs.mkdir(`${distRoot}${path.dirname(dist)}`, { recursive: true }).then(async () => {
32
+ await fs.writeFile(`${distRoot}${dist}`, css)
33
+ console.log(`generate ${src} => ${distRoot}${dist}`)
34
+ return cacheBuster[key]
35
+ })
36
+ }
37
+
38
+ /**
39
+ * 次のような記述を想定している。
40
+ * <link rel="stylesheet" href="${/css/layout.css:/css/base.css,/css/page.css}">
41
+ * href の記述は ${dist:src} の関係になっている。
42
+ *
43
+ * これを
44
+ * <link rel="stylesheet" href="/css/layout.css?t=daddfiao3901239dasei12b">
45
+ * に変換する。
46
+ *
47
+ * @param {string} text
48
+ * @return {string}
49
+ */
50
+ const applyCss = async (text) => {
51
+ const target = [...text.matchAll(/\${([\w-_/]+\.css)<<([\w-_/,.]+\.css)}/g)].map(val => {
52
+ return { matched: val[0], dist: val[1], src: val[2] }
53
+ })
54
+ for (const cssDist of target) {
55
+ const cacheBuster = await cssGenerator(cssDist.src, cssDist.dist)
56
+ text = text.replace(cssDist.matched, `${process.env.RELATIVE_PATH || ''}${cssDist.dist}?${cacheBusterQuery}=${cacheBuster}`)
57
+ }
58
+ return text
59
+ }
60
+
61
+ export default applyCss
package/lib/dir.js ADDED
@@ -0,0 +1,15 @@
1
+ const rootDir = process.cwd()
2
+ const srcDir = `${rootDir}/${process.env.SRC_DIR}`
3
+ const distDir = `${rootDir}/${process.env.DIST_DIR}`
4
+ const templateDir = `${rootDir}/${process.env.SRC_DIR}/template`
5
+ const cssDir = `${rootDir}/${process.env.SRC_DIR}/css`
6
+ const cacheDir = `${rootDir}/.cache`
7
+
8
+ export {
9
+ rootDir,
10
+ srcDir,
11
+ distDir,
12
+ templateDir,
13
+ cssDir,
14
+ cacheDir,
15
+ }
@@ -0,0 +1,35 @@
1
+ "use strict"
2
+ import fs from "node:fs/promises";
3
+ import path from 'path'
4
+ import { minifyHtml } from './minify.js'
5
+ import render from './applyTemplate.js'
6
+
7
+ const distribute = async (data, srcDir, distDir) => {
8
+ if (data['__deleted']) {
9
+ for (const i in data['__deleted']) {
10
+ console.log(`unlink ${distDir}${data['__deleted'][i].__output}`)
11
+ fs.unlink(`${distDir}${data['__deleted'][i].__output}`)
12
+ }
13
+ delete data['__deleted']
14
+ }
15
+ for (const name in data) {
16
+ const template = data[name].template
17
+ const rendered = await render(template, data[name])
18
+ let writeTo = `${distDir}${data[name].__output}`
19
+ fs.mkdir(path.dirname(writeTo), { recursive: true}).then(() => {
20
+ fs.writeFile(writeTo, minifyHtml(rendered))
21
+ console.log(`generate ${writeTo}`)
22
+ })
23
+ }
24
+ const distributeRaw = process.env.DISTRIBUTE_RAW.split(',')
25
+ distributeRaw.forEach((copyDir) => {
26
+ fs.readdir(`${srcDir}/${copyDir}/`).then(async files => {
27
+ await fs.stat(`${distDir}/${copyDir}/`).catch(async err => await fs.mkdir(`${distDir}/${copyDir}/`))
28
+ files.forEach(file => {
29
+ fs.copyFile(`${srcDir}/${copyDir}/${file}`, `${distDir}/${copyDir}/${file}`)
30
+ })
31
+ })
32
+ })
33
+ }
34
+
35
+ export default distribute
package/lib/filter.js ADDED
@@ -0,0 +1,167 @@
1
+ import * as helper from '../helper/index.js'
2
+ import includeFilter from './includeFilter.js'
3
+ import { srcDir } from './dir.js'
4
+
5
+ /**
6
+ * @param {string} text
7
+ * @params {object} variables
8
+ * @return {text}
9
+ */
10
+ const replaceVariablesFilter = (text, variables) => {
11
+ const matched = [...text.matchAll(/{{\s?([\w\d-_\(\)]+)\s?}}/g)]
12
+ const replace = Object.fromEntries(matched.map(match => [match[1].toLowerCase(), match[0]]))
13
+ let replaced = text
14
+ for (const elm in replace) {
15
+ const toBeReplace = replace[elm]
16
+ const toBeReplaceScript = toBeReplace.match(/([\w\d_-]+)\((.+)\)/)
17
+ if (toBeReplaceScript) {
18
+ const func = toBeReplaceScript[1]
19
+ const variable = toBeReplaceScript[2]
20
+ if (!helper[func]) {
21
+ throw new Exception('helper function is missing. function name: ' + func);
22
+ }
23
+ const replaceText = helper[func](variables[variable.toLowerCase()])
24
+ replaced = replaced.replaceAll(toBeReplace, replaceText)
25
+ } else {
26
+ replaced = replaced.replaceAll(toBeReplace, variables[elm])
27
+ }
28
+ }
29
+ return replaced
30
+ }
31
+
32
+ /**
33
+ * @param {string} condition
34
+ * @params {object} variables
35
+ * @return {bool}
36
+ */
37
+ const ifConditionEvaluator = (condition, variables) => {
38
+ if (condition.includes('=')) {
39
+ const segmented = condition.match(/(?<left>[\S]+)\s(?<operator>!=|==)\s(?<right>[\S]+)/)
40
+ let {left, operator, right} = segmented.groups
41
+ if (variables.hasOwnProperty(left)) {
42
+ left = variables[left]
43
+ } else {
44
+ try {
45
+ left = eval(left)
46
+ } catch (e) {
47
+ left = undefined
48
+ }
49
+ }
50
+ if (variables.hasOwnProperty(right)) {
51
+ right = variables[right]
52
+ } else {
53
+ try {
54
+ right = eval(right)
55
+ } catch (e) {
56
+ right = undefined
57
+ }
58
+ }
59
+ switch (operator) {
60
+ case '==':
61
+ return left == right
62
+ case '!=':
63
+ return left != right
64
+ }
65
+ } else {
66
+ if (variables.hasOwnProperty(condition)) {
67
+ return !!variables[condition]
68
+ }
69
+ return false
70
+ }
71
+ }
72
+
73
+ /**
74
+ * @param {string} text
75
+ * @param {object} variables
76
+ * @returns {string}
77
+ */
78
+ const replaceIfFilter = (text, variables) => {
79
+ const ifRegexp = new RegExp(/(\{|\<)if\s(?<condition>[\s\S]+?)(}|>)(?<content>[\s\S]*?)((\{|\<)else(\}|\>)(?<elsecontent>[\s\S]*?))?(\{|\<)\/if(\>|})/g)
80
+ const matched = [...text.matchAll(ifRegexp)]
81
+ for (const item of matched) {
82
+ const target = item[0]
83
+ const content = item.groups.content
84
+ const elseContent = item.groups.elsecontent
85
+ if (ifConditionEvaluator(item.groups.condition, variables)) {
86
+ text = text.replace(target, content)
87
+ } else if (elseContent) {
88
+ text = text.replace(target, elseContent)
89
+ } else {
90
+ text = text.replace(target, '')
91
+ }
92
+ }
93
+ return text
94
+ }
95
+
96
+ /**
97
+ * 配列を再帰的に順不同リストに変換する
98
+ * @param {Array|string} arrayOrText
99
+ * @returns {mixed}
100
+ */
101
+ const arrayToList = (arrayOrText) => {
102
+ if (typeof arrayOrText === 'string') {
103
+ return `<li>${arrayOrText}</li>`
104
+ }
105
+ if (Array.isArray(arrayOrText)) {
106
+ let resultListText = '<ul>'
107
+ for (const item of arrayOrText) {
108
+ if (Array.isArray(item)) {
109
+ resultListText += `<li>${arrayToList(item)}</li>`
110
+ } else {
111
+ resultListText += `<li>${item}</li>`
112
+ }
113
+ }
114
+ resultListText += '</ul>'
115
+ arrayOrText = resultListText
116
+ }
117
+ return arrayOrText
118
+ }
119
+
120
+ const replaceScriptFilter = async (text, variables) => {
121
+ let replaced = text
122
+ const scriptRegexp = new RegExp(/({script}|\<script\s.*type="ssg".*>)(?<script>[\s\S]*?)(\{\/script}|\<\/script>)/g)
123
+ const scripts = [...text.matchAll(scriptRegexp)].map((matched) => {
124
+ return {
125
+ replace: matched[0],
126
+ script: matched.groups.script.trim("\n"),
127
+ }
128
+ })
129
+ for (const script of scripts) {
130
+ let helperMerged = {...helper}
131
+ // env.HELPER が定義されていれば追加ヘルパーとして扱う
132
+ if (process.env.HELPER) {
133
+ const additional = await import(`${srcDir}/${process.env.HELPER}`)
134
+ helperMerged = Object.assign(helperMerged, additional)
135
+ }
136
+ let result = new Function('helper', 'variables', script.script)(helperMerged, variables)
137
+ if (result instanceof Promise) {
138
+ result = await result
139
+ }
140
+ if (typeof result === 'undefined') {
141
+ result = null
142
+ }
143
+ if (Array.isArray(result)) {
144
+ result = arrayToList(result)
145
+ } else if (typeof result === 'object') {
146
+ const resultText = []
147
+ for (const key in result) {
148
+ resultText.push(`<h3>${key}</h3>`)
149
+ if (Array.isArray(result[key])) {
150
+ resultText.push(arrayToList(result[key]))
151
+ } else {
152
+ resultText.push(`<p>${result[key]}</p>`)
153
+ }
154
+ }
155
+ result = resultText.join('\n')
156
+ }
157
+ replaced = replaced.replace(script.replace, result)
158
+ }
159
+ return replaced
160
+ }
161
+
162
+ export {
163
+ replaceIfFilter,
164
+ replaceScriptFilter,
165
+ replaceVariablesFilter,
166
+ includeFilter
167
+ }