mikel-press 0.22.1 → 0.23.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 (3) hide show
  1. package/README.md +7 -3
  2. package/index.js +68 -33
  3. package/package.json +1 -4
package/README.md CHANGED
@@ -10,13 +10,13 @@
10
10
  To install **mikel-press**, ensure you have [Node.js](https://nodejs.org) installed on your system. Then, add this package as a dependency to your project using **yarn**:
11
11
 
12
12
  ```bash
13
- $ yarn add --dev mikel-press
13
+ $ yarn add --dev mikel mikel-press
14
14
  ```
15
15
 
16
16
  Or **npm**:
17
17
 
18
18
  ```bash
19
- $ npm install --dev mikel-press
19
+ $ npm install --dev mikel mikel-press
20
20
  ```
21
21
 
22
22
  ## Directory structure
@@ -52,18 +52,22 @@ A basic **mikel-press** directory structure looks like this:
52
52
  | `source` | The path to the directory containing the site folders. | `"."` |
53
53
  | `destination` | The output directory where the generated static site will be saved. | `"www"` |
54
54
  | `extensions` | List of file extensions to process. | `[".html"]` |
55
- | `mikelOptions` | An object containing custom configuration for the **mikel** templating engine. | `{}` |
55
+ | `template` | An instance of `mikel.create` to compile templates. | - |
56
56
  | `plugins` | A list of plugins used to extend the functionality of **mikel-press**. | `[]` |
57
57
  | `*` | Any other properties passed in config will be available as `site.*` inside each page template. | - |
58
58
 
59
59
  Here is an example configuration object:
60
60
 
61
61
  ```javascript
62
+ import mikel from "mikel";
62
63
  import press from "mikel-press";
63
64
 
64
65
  press({
65
66
  source: ".",
66
67
  destination: "./www",
68
+ template: mikel.create("", {
69
+ // define your custom helpers and functions here
70
+ }),
67
71
  title: "Hello world",
68
72
  description: "My awesome site",
69
73
  plugins: [
package/index.js CHANGED
@@ -1,6 +1,10 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
- import mikel from "mikel";
3
+
4
+ // @description get all plugins of the given type
5
+ const getPlugins = (context, name) => {
6
+ return context.plugins.filter(plugin => typeof plugin[name] === "function");
7
+ };
4
8
 
5
9
  // @description press main function
6
10
  // @param {Object} config - configuration object
@@ -8,28 +12,35 @@ import mikel from "mikel";
8
12
  // @param {String} config.destination - destination folder to save the files
9
13
  // @param {Array} config.plugins - list of plugins to apply
10
14
  const press = (config = {}) => {
11
- const {source, destination, plugins, extensions, exclude, mikelOptions, ...otherConfig} = config;
15
+ const context = press.createContext(config);
16
+ press.buildContext(context, context.nodes);
17
+ if (config.watch === true) {
18
+ press.watchContext(context);
19
+ }
20
+ };
21
+
22
+ // @description create a context object
23
+ press.createContext = (config = {}) => {
24
+ const {source, destination, plugins, extensions, exclude, template, watch, ...otherConfig} = config;
12
25
  const context = Object.freeze({
13
26
  config: otherConfig,
14
27
  source: path.resolve(source || "."),
15
28
  destination: path.resolve(destination || "./www"),
16
29
  extensions: extensions || [".html"],
17
30
  exclude: exclude || ["node_modules", ".git", ".gitignore", ".github"],
18
- template: mikel.create("{{>content}}", mikelOptions || {}),
31
+ template: template,
19
32
  plugins: [
20
33
  press.SourcePlugin({folder: ".", label: press.LABEL_PAGE}),
21
34
  ...plugins,
22
35
  ],
23
36
  nodes: [],
24
37
  });
25
- const getPlugins = name => context.plugins.filter(plugin => typeof plugin[name] === "function");
26
- // 0. initialize
27
- getPlugins("init").forEach(plugin => {
38
+ getPlugins(context, "init").forEach(plugin => {
28
39
  return plugin.init(context);
29
40
  });
30
- // 1. load nodes into context
41
+ // load nodes into context
31
42
  const nodesPaths = new Set(); // prevent adding duplicated nodes
32
- getPlugins("load").forEach(plugin => {
43
+ getPlugins(context, "load").forEach(plugin => {
33
44
  [plugin.load(context) || []].flat().forEach(node => {
34
45
  if (nodesPaths.has(node.source)) {
35
46
  throw new Error(`File ${node.source} has been already processed by another plugin`);
@@ -38,29 +49,35 @@ const press = (config = {}) => {
38
49
  nodesPaths.add(node.source);
39
50
  });
40
51
  });
41
- // 2. transform nodes
42
- getPlugins("transform").forEach(plugin => {
52
+ return context;
53
+ };
54
+
55
+ // @description build the provided context
56
+ press.buildContext = (context, nodesToBuild = null) => {
57
+ const nodes = Array.isArray(nodesToBuild) ? nodesToBuild : context.nodes;
58
+ // 1. transform nodes
59
+ getPlugins(context, "transform").forEach(plugin => {
43
60
  // special hook to initialize the transform plugin
44
61
  if (typeof plugin.beforeTransform === "function") {
45
62
  plugin.beforeTransform(context);
46
63
  }
47
64
  // run the transform in all nodes
48
- context.nodes.forEach((node, _, allNodes) => {
65
+ nodes.forEach((node, _, allNodes) => {
49
66
  return plugin.transform(context, node, allNodes);
50
67
  });
51
68
  });
52
- // 3. filter nodes and get only the ones that are going to be emitted
53
- const shouldEmitPlugins = getPlugins("shouldEmit");
54
- const filteredNodes = context.nodes.filter((node, _, allNodes) => {
69
+ // 2. filter nodes and get only the ones that are going to be emitted
70
+ const shouldEmitPlugins = getPlugins(context, "shouldEmit");
71
+ const filteredNodes = nodes.filter((node, _, allNodes) => {
55
72
  return shouldEmitPlugins.every(plugin => {
56
73
  return !!plugin.shouldEmit(context, node, allNodes);
57
74
  });
58
75
  });
59
- // 4. before emit
60
- getPlugins("beforeEmit").forEach(plugin => {
76
+ // 3. before emit
77
+ getPlugins(context, "beforeEmit").forEach(plugin => {
61
78
  return plugin.beforeEmit(context);
62
79
  });
63
- // 5. emit each node
80
+ // 4. emit each node
64
81
  filteredNodes.forEach(node => {
65
82
  // 1. if node has been processed (aka node.content is an string), write the file
66
83
  if (typeof node.content === "string") {
@@ -73,6 +90,17 @@ const press = (config = {}) => {
73
90
  });
74
91
  };
75
92
 
93
+ // @description start a watch on the current context
94
+ press.watchContext = (context, options = {}) => {
95
+ const labelsToWatch = options.labels || [press.LABEL_PAGE, press.LABEL_PARTIAL, press.LABEL_DATA];
96
+ const nodesToRebuild = context.nodes.filter(node => labelsToWatch.includes(node.label));
97
+ const rebuild = () => press.buildContext(context, nodesToRebuild);
98
+ // create a watch for each registered node in the context
99
+ nodesToRebuild.forEach(node => {
100
+ press.utils.watch(node.source, rebuild);
101
+ });
102
+ };
103
+
76
104
  // @description general utilities
77
105
  press.utils = {
78
106
  // @description read a file from disk
@@ -107,6 +135,19 @@ press.utils = {
107
135
  .filter(file => (extensions === "*" || extensions.includes(path.extname(file))) && !exclude.includes(file))
108
136
  .filter(file => fs.statSync(path.join(folder, file)).isFile());
109
137
  },
138
+ // @description watch for file changes
139
+ // @param {String} filePath path to the file to watch
140
+ // @param {Function} listener method to listen for file changes
141
+ watch: (filePath, listener) => {
142
+ let lastModifiedTime = null;
143
+ fs.watch(filePath, "utf8", () => {
144
+ const modifiedTime = fs.statSync(filePath).mtimeMs;
145
+ if (lastModifiedTime !== modifiedTime) {
146
+ lastModifiedTime = modifiedTime;
147
+ return listener(filePath);
148
+ }
149
+ });
150
+ },
110
151
  // @description frontmatter parser
111
152
  // @params {String} content content to parse
112
153
  // @params {Function} parser parser function to use
@@ -130,7 +171,7 @@ press.LABEL_PARTIAL = "asset/partial";
130
171
 
131
172
  // @description source plugin
132
173
  press.SourcePlugin = (options = {}) => {
133
- const shouldEmit = options?.shouldEmit ?? true;
174
+ const shouldEmit = options?.emit ?? true, shouldRead = options.read ?? true;
134
175
  const processedNodes = new Set();
135
176
  return {
136
177
  name: "SourcePlugin",
@@ -145,10 +186,14 @@ press.SourcePlugin = (options = {}) => {
145
186
  label: options.label || press.LABEL_PAGE,
146
187
  path: path.join(options?.basePath || ".", file),
147
188
  url: path.normalize("/" + path.join(options?.basePath || ".", file)),
148
- content: press.utils.read(path.join(folder, file)),
149
189
  };
150
190
  });
151
191
  },
192
+ transform: (context, node) => {
193
+ if (processedNodes.has(node.source) && shouldRead) {
194
+ node.content = press.utils.read(node.source);
195
+ }
196
+ },
152
197
  shouldEmit: (context, node) => {
153
198
  return !processedNodes.has(node.source) || shouldEmit;
154
199
  },
@@ -157,27 +202,17 @@ press.SourcePlugin = (options = {}) => {
157
202
 
158
203
  // @description data plugin
159
204
  press.DataPlugin = (options = {}) => {
160
- return press.SourcePlugin({folder: "./data", shouldEmit: false, extensions: [".json"], label: press.LABEL_DATA, ...options});
205
+ return press.SourcePlugin({folder: "./data", emit: false, extensions: [".json"], label: press.LABEL_DATA, ...options});
161
206
  };
162
207
 
163
208
  // @description partials plugin
164
209
  press.PartialsPlugin = (options = {}) => {
165
- return press.SourcePlugin({folder: "./partials", shouldEmit: false, extensions: [".html"], label: press.LABEL_PARTIAL, ...options});
210
+ return press.SourcePlugin({folder: "./partials", emit: false, extensions: [".html"], label: press.LABEL_PARTIAL, ...options});
166
211
  };
167
212
 
168
213
  // @description assets plugin
169
214
  press.AssetsPlugin = (options = {}) => {
170
- return {
171
- name: "AssetsPlugin",
172
- load: context => {
173
- const folder = path.join(context.source, options?.folder || "./assets");
174
- return press.utils.readdir(folder, options?.extensions || "*", options?.exclude || context.exclude).map(file => ({
175
- source: path.join(folder, file),
176
- label: options.label || press.LABEL_ASSET,
177
- path: path.join(options?.basePath || ".", file),
178
- }));
179
- },
180
- };
215
+ return press.SourcePlugin({folder: "./assets", read: false, extensions: "*", label: press.LABEL_ASSET, ...options});
181
216
  };
182
217
 
183
218
  // @description frontmatter plugin
@@ -237,7 +272,7 @@ press.ContentPagePlugin = (siteData = {}) => {
237
272
  press.UsePlugin = mikelPlugin => {
238
273
  return {
239
274
  name: "UsePlugin",
240
- init: () => {
275
+ init: context => {
241
276
  context.template.use(mikelPlugin);
242
277
  },
243
278
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mikel-press",
3
3
  "description": "A tiny and fast static site generator based on mikel templating",
4
- "version": "0.22.1",
4
+ "version": "0.23.0",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": {
@@ -18,9 +18,6 @@
18
18
  "engines": {
19
19
  "node": ">=20"
20
20
  },
21
- "dependencies": {
22
- "mikel": "^0.22.1"
23
- },
24
21
  "files": [
25
22
  "README.md",
26
23
  "index.js"