metalsmith-prism 5.0.1 → 5.0.4

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 CHANGED
@@ -1,25 +1,35 @@
1
1
  # metalsmith-prism
2
2
 
3
- A Metalsmith plugin that **adds Prism specific HTML markup** to code sections for syntax coloring.
3
+ A Metalsmith plugin that **adds Prism specific HTML markup** to code sections for syntax coloring. Now with full dual module support for both **ESM** and **CommonJS** environments.
4
4
 
5
- [![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square&label=license)](http://opensource.org/licenses/MIT)
6
- [![NPM](http://img.shields.io/npm/v/metalsmith-prism.svg?style=flat-square&label=npm)](https://npmjs.org/package/metalsmith-prism)
7
- [![Linux Passing](https://img.shields.io/travis/Availity/metalsmith-prism.svg?style=flat-square&label=linux)](https://travis-ci.org/Availity/metalsmith-prism)
8
- [![Windows Passing](https://img.shields.io/appveyor/ci/robmcguinness/metalsmith-prism.svg?style=flat-square&label=windows)](https://ci.appveyor.com/project/robmcguinness/metalsmith-prism)
5
+ [![metalsmith:plugin][metalsmith-badge]][metalsmith-url]
6
+ [![npm: version][npm-badge]][npm-url]
7
+ [![license: MIT][license-badge]][license-url]
8
+ [![coverage][coverage-badge]][coverage-url]
9
+ [![ESM/CommonJS][modules-badge]][npm-url]
10
+
11
+ ## Dual Module Support (ESM and CommonJS)
12
+
13
+ This plugin supports both ESM and CommonJS environments with no configuration needed:
14
+
15
+ - ESM: `import prism from 'metalsmith-prism'`
16
+ - CommonJS: `const prism = require('metalsmith-prism')`
17
+
18
+ The package detects your environment automatically and provides the appropriate module format. This makes it compatible with both modern ESM projects and legacy CommonJS codebases.
9
19
 
10
20
  While this plugin adds all the required Prism HTML markup, **prism.css** must be included on the page to provide the syntax coloring. The plugin:
11
21
 
22
+ - Supports both ESM and CommonJS environments
12
23
  - Automatically handles language dependencies
13
24
  - Supports HTML entity decoding
14
25
  - Can add line numbers
15
26
  - Works seamlessly with Markdown code blocks
16
- - Supports all Prism.js languages
27
+ - Supports all Prism.js language
17
28
 
18
29
  ## Requirements
19
30
 
20
- - Node `>= 18.x.x`
21
- - NPM `>= 9.x.x`
22
- - Metalsmith `>= v2.6.x`
31
+ - Node `>= 18.0.0`
32
+ - Metalsmith `>= v2.6.0`
23
33
 
24
34
  ## Quick Start
25
35
 
@@ -35,7 +45,7 @@ metalsmith(__dirname).use(
35
45
  prism({
36
46
  decode: true, // Decode HTML entities
37
47
  lineNumbers: true, // Show line numbers
38
- preLoad: ['java'], // Pre-load language dependencies
48
+ preLoad: ['java'] // Pre-load language dependencies
39
49
  })
40
50
  );
41
51
  ```
@@ -74,21 +84,44 @@ The css files can be downloaded from the [Prism website](https://prismjs.com/dow
74
84
 
75
85
  ### Add `metalsmith-prism` plugin to metalsmith
76
86
 
87
+ **ESM:**
88
+
77
89
  ```js
78
- const metalsmith = require('metalsmith');
90
+ import Metalsmith from 'metalsmith';
91
+ import prism from 'metalsmith-prism';
92
+
93
+ Metalsmith(__dirname).use(prism()).build();
94
+ ```
95
+
96
+ **CommonJS:**
97
+
98
+ ```js
99
+ const Metalsmith = require('metalsmith');
79
100
  const prism = require('metalsmith-prism');
80
101
 
81
- metalsmith(__dirname).use(prism()).build();
102
+ Metalsmith(__dirname).use(prism()).build();
82
103
  ```
83
104
 
84
105
  ### To use with Markdown code blocks rendered by [@metalsmith/markdown](https://github.com/metalsmith/markdown)
85
106
 
107
+ **ESM:**
108
+
109
+ ```js
110
+ import Metalsmith from 'metalsmith';
111
+ import markdown from '@metalsmith/markdown';
112
+ import prism from 'metalsmith-prism';
113
+
114
+ Metalsmith(__dirname).use(markdown()).use(prism()).build();
115
+ ```
116
+
117
+ **CommonJS:**
118
+
86
119
  ```js
87
- const metalsmith = require('metalsmith');
120
+ const Metalsmith = require('metalsmith');
88
121
  const markdown = require('@metalsmith/markdown');
89
122
  const prism = require('metalsmith-prism');
90
123
 
91
- metalsmith(__dirname).use(markdown()).use(prism()).build();
124
+ Metalsmith(__dirname).use(markdown()).use(prism()).build();
92
125
  ```
93
126
 
94
127
  ## Language support
@@ -106,7 +139,7 @@ Always decode the html entities when processing language of type `markup`
106
139
  ```js
107
140
  Metalsmith(__dirname).use(
108
141
  prism({
109
- decode: true,
142
+ decode: true
110
143
  })
111
144
  );
112
145
  ```
@@ -117,8 +150,8 @@ Adds the additional HTML markup so line numbers can be added via the line-number
117
150
 
118
151
  ```javascript
119
152
  Metalsmith(__dirname).use(
120
- metalsmithPrism({
121
- lineNumbers: true,
153
+ prism({
154
+ lineNumbers: true
122
155
  })
123
156
  );
124
157
  ```
@@ -132,7 +165,7 @@ Useful for loading syntax that extends other language components that are not au
132
165
  ```javascript
133
166
  Metalsmith(__dirname).use(
134
167
  prism({
135
- preLoad: ['java', 'scala'],
168
+ preLoad: ['java', 'scala']
136
169
  })
137
170
  );
138
171
  ```
@@ -143,14 +176,20 @@ To enable debug logs, set the `DEBUG` environment variable to `metalsmith-prism`
143
176
 
144
177
  Linux/Mac:
145
178
 
146
- ```
147
- DEBUG=metalsmith-prism
179
+ ```bash
180
+ DEBUG=metalsmith-prism npm test
148
181
  ```
149
182
 
150
183
  Windows:
151
184
 
185
+ ```bash
186
+ set "DEBUG=metalsmith-prism" && npm test
152
187
  ```
153
- set "DEBUG=metalsmith-prism"
188
+
189
+ Or use the test:debug script:
190
+
191
+ ```bash
192
+ npm run test:debug
154
193
  ```
155
194
 
156
195
  ## CLI Usage
@@ -168,10 +207,29 @@ Add `metalsmith-prism` key to your `metalsmith.json` plugins key
168
207
  }
169
208
  ```
170
209
 
210
+ ## Test Coverage
211
+
212
+ This project maintains high statement and line coverage for the source code. Coverage is verified during the release process using the c8 coverage tool.
213
+
171
214
  ## Credits
172
215
 
173
- [Robert McGuinness](https://github.com/robmcguinness) - for the initial implementation of the plugin.
216
+ - [Robert McGuinness](https://github.com/robmcguinness) - for the initial implementation of the plugin.
217
+ - [Werner Glinka](https://github.com/wernerglinka) - current maintainer.
174
218
 
175
219
  ## License
176
220
 
177
221
  Code released under [the MIT license](https://github.com/wernerglinka/metalsmith-prism/blob/main/LICENSE).
222
+
223
+ [coverage-badge]: https://img.shields.io/badge/coverage-94%25-brightgreen
224
+ [coverage-url]: #test-coverage
225
+ [modules-badge]: https://img.shields.io/badge/modules-ESM%2FCJS-blue
226
+ [npm-url]: https://npmjs.org/package/metalsmith-prism
227
+ [npm-badge]: https://img.shields.io/npm/v/metalsmith-prism.svg
228
+ [npm-url]: https://www.npmjs.com/package/metalsmith-prism
229
+ [metalsmith-badge]: https://img.shields.io/badge/metalsmith-plugin-green.svg?longCache=true
230
+ [metalsmith-url]: https://metalsmith.io
231
+ [license-badge]: https://img.shields.io/github/license/wernerglinka/metalsmith-prism
232
+ [license-url]: LICENSE
233
+ [coverage-badge]: https://img.shields.io/badge/test%20coverage-98%25-brightgreen
234
+ [coverage-url]: #test-coverage
235
+ [modules-badge]: https://img.shields.io/badge/modules-ESM%2FCJS-blue
package/lib/index.cjs ADDED
@@ -0,0 +1,209 @@
1
+ 'use strict';
2
+
3
+ var cheerio = require('cheerio');
4
+ var path = require('path');
5
+ var Prism = require('prismjs');
6
+ var loadLanguages = require('prismjs/components/index.js');
7
+ var he = require('he');
8
+ var debugLib = require('debug');
9
+
10
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
11
+
12
+ var Prism__default = /*#__PURE__*/_interopDefaultLegacy(Prism);
13
+ var loadLanguages__default = /*#__PURE__*/_interopDefaultLegacy(loadLanguages);
14
+ var he__default = /*#__PURE__*/_interopDefaultLegacy(he);
15
+ var debugLib__default = /*#__PURE__*/_interopDefaultLegacy(debugLib);
16
+
17
+ const debug = debugLib__default["default"]('metalsmith-prism');
18
+
19
+ // Import languages from Prism's default export
20
+ const {
21
+ languages
22
+ } = Prism__default["default"];
23
+
24
+ /**
25
+ * Check if a file is HTML based on its extension
26
+ * @param {string} filePath - Path to the file
27
+ * @returns {boolean} - True if the file has an HTML extension
28
+ */
29
+ const isHTMLFile = filePath => {
30
+ const extension = path.extname(filePath).toLowerCase();
31
+ return ['.html', '.htm'].includes(extension);
32
+ };
33
+
34
+ /**
35
+ * @typedef Options
36
+ * @property {boolean} [decode=false] - Whether to decode HTML entities
37
+ * @property {boolean} [lineNumbers=false] - Whether to add line numbers
38
+ * @property {string[]} [preLoad=[]] - Languages to preload
39
+ */
40
+
41
+ /**
42
+ * Metalsmith plugin to highlight code syntax with PrismJS
43
+ *
44
+ * This plugin finds all code blocks in HTML files that have language-* classes
45
+ * and applies Prism.js syntax highlighting to them. It can also add line numbers
46
+ * and handle HTML entity decoding.
47
+ *
48
+ * @param {Options} [options] - Configuration options
49
+ * @param {boolean} [options.decode=false] - Whether to decode HTML entities in code blocks
50
+ * @param {boolean} [options.lineNumbers=false] - Whether to add line numbers to code blocks
51
+ * @param {string[]} [options.preLoad=[]] - Languages to preload before processing
52
+ * @returns {import('metalsmith').Plugin} - A metalsmith plugin function
53
+ * @example
54
+ * // Basic usage
55
+ * metalsmith.use(prism());
56
+ *
57
+ * // With options
58
+ * metalsmith.use(prism({
59
+ * decode: true,
60
+ * lineNumbers: true,
61
+ * preLoad: ['java', 'scala']
62
+ * }));
63
+ */
64
+ const metalsmithPrism = (options = {}) => {
65
+ // Create a new options object with defaults
66
+ const opts = {
67
+ decode: false,
68
+ lineNumbers: false,
69
+ preLoad: [],
70
+ ...options
71
+ };
72
+
73
+ // Track loaded languages to avoid duplicate loading
74
+ const loadedLanguages = new Set();
75
+
76
+ // Always load PHP by default
77
+ debug('Loading PHP by default');
78
+ try {
79
+ loadLanguages__default["default"](['php']);
80
+ loadedLanguages.add('php');
81
+ } catch (e) {
82
+ debug('Failed to load PHP:', e);
83
+ }
84
+ if (opts.preLoad && opts.preLoad.length) {
85
+ debug('Preloading languages:', opts.preLoad);
86
+ opts.preLoad.forEach(language => {
87
+ if (!loadedLanguages.has(language)) {
88
+ try {
89
+ loadLanguages__default["default"]([language]);
90
+ loadedLanguages.add(language);
91
+ debug(`Successfully preloaded language: ${language}`);
92
+ } catch (e) {
93
+ console.warn(`Failed to preload prism syntax: ${language}!`, e);
94
+ debug(`Error preloading language ${language}:`, e);
95
+ }
96
+ } else {
97
+ debug(`Language ${language} already loaded, skipping`);
98
+ }
99
+ });
100
+ }
101
+
102
+ /**
103
+ * Require optional language package
104
+ * @param {string} language
105
+ * @param {Set} loadedLanguages
106
+ */
107
+ const requireLanguage = (language, loadedLanguages) => {
108
+ if (loadedLanguages.has(language) || languages[language]) {
109
+ debug(`Language ${language} already available, skipping load`);
110
+ return;
111
+ }
112
+ debug(`Loading language on-demand: ${language}`);
113
+ try {
114
+ loadLanguages__default["default"]([language]);
115
+ loadedLanguages.add(language);
116
+ debug(`Successfully loaded language: ${language}`);
117
+ } catch (e) {
118
+ console.warn(`Failed to load prism syntax: ${language}!`, e);
119
+ debug(`Error loading language ${language}:`, e);
120
+ }
121
+ };
122
+
123
+ // Set up line numbers functionality
124
+ const NEW_LINE_EXP = /\n(?!$)/g;
125
+ let lineNumbersWrapper;
126
+
127
+ // Only set up the hook if line numbers are requested
128
+ if (opts.lineNumbers) {
129
+ debug('Setting up line numbers hook');
130
+ Prism__default["default"].hooks.add('after-tokenize', env => {
131
+ const match = env.code.match(NEW_LINE_EXP);
132
+ const linesNum = match ? match.length + 1 : 1;
133
+ debug(`Counted ${linesNum} lines for line numbers`);
134
+ const lines = new Array(linesNum + 1).join('<span></span>');
135
+ lineNumbersWrapper = `<span aria-hidden="true" class="line-numbers-rows">${lines}</span>`;
136
+ });
137
+ }
138
+ return (files, metalsmith, done) => {
139
+ debug('Starting metalsmith-prism plugin');
140
+ debug('Options:', opts);
141
+
142
+ // Call done asynchronously to avoid blocking
143
+ setImmediate(done);
144
+ try {
145
+ Object.keys(files).forEach(file => {
146
+ if (!isHTMLFile(file)) {
147
+ return;
148
+ }
149
+ debug(`Processing HTML file: ${file}`);
150
+ const contents = files[file].contents.toString();
151
+ const $ = cheerio.load(contents, {
152
+ decodeEntities: false
153
+ });
154
+ let highlighted = false;
155
+ const code = $('code');
156
+ if (!code.length) {
157
+ debug(`No code blocks found in ${file}`);
158
+ return;
159
+ }
160
+ debug(`Found ${code.length} code blocks in ${file}`);
161
+ code.each(function () {
162
+ const $this = $(this);
163
+ const className = $this.attr('class') || '';
164
+ const targets = className.split('language-');
165
+ let addLineNumbers = false;
166
+ if (targets.length > 1) {
167
+ const $pre = $this.parent('pre');
168
+ if ($pre) {
169
+ // Copy className to <pre> container
170
+ $pre.addClass(className);
171
+ if (opts.lineNumbers) {
172
+ $pre.addClass('line-numbers');
173
+ addLineNumbers = true;
174
+ debug('Adding line numbers');
175
+ }
176
+ }
177
+ highlighted = true;
178
+ let language = targets[1];
179
+ debug(`Detected language: ${language}`);
180
+ requireLanguage(language, loadedLanguages);
181
+ if (!languages[language]) {
182
+ debug(`Language ${language} not available, falling back to markup`);
183
+ language = 'markup';
184
+ }
185
+ const html = language === 'markup' && !opts.decode ? $this.html() : he__default["default"].decode($this.html());
186
+ debug(`HTML decoding ${opts.decode ? 'applied' : 'not applied'} for language ${language}`);
187
+ debug(`Highlighting code with language: ${language}`);
188
+ const highlightedCode = Prism__default["default"].highlight(html, languages[language]);
189
+ $this.html(addLineNumbers ? highlightedCode + lineNumbersWrapper : highlightedCode);
190
+ }
191
+ });
192
+ if (highlighted) {
193
+ debug(`Updating contents of ${file} with highlighted code`);
194
+ files[file].contents = Buffer.from($.html());
195
+ } else {
196
+ debug(`No code was highlighted in ${file}`);
197
+ }
198
+ });
199
+ debug('Completed metalsmith-prism plugin');
200
+ } catch (error) {
201
+ debug('Error in metalsmith-prism plugin:', error);
202
+ // We can't call done(error) here because done has already been called
203
+ console.error('Error processing files:', error);
204
+ }
205
+ };
206
+ };
207
+
208
+ module.exports = metalsmithPrism;
209
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","sources":["../src/index.js"],"sourcesContent":["import { load } from 'cheerio';\nimport { extname } from 'path';\nimport Prism from 'prismjs';\nimport loadLanguages from 'prismjs/components/index.js';\nimport he from 'he';\nimport debugLib from 'debug';\n\nconst debug = debugLib( 'metalsmith-prism' );\n\n// Import languages from Prism's default export\nconst { languages } = Prism;\n\n/**\n * Check if a file is HTML based on its extension\n * @param {string} filePath - Path to the file\n * @returns {boolean} - True if the file has an HTML extension\n */\nconst isHTMLFile = ( filePath ) => {\n const extension = extname( filePath ).toLowerCase();\n return [ '.html', '.htm' ].includes( extension );\n};\n\n/**\n * @typedef Options\n * @property {boolean} [decode=false] - Whether to decode HTML entities\n * @property {boolean} [lineNumbers=false] - Whether to add line numbers\n * @property {string[]} [preLoad=[]] - Languages to preload\n */\n\n/**\n * Metalsmith plugin to highlight code syntax with PrismJS\n *\n * This plugin finds all code blocks in HTML files that have language-* classes\n * and applies Prism.js syntax highlighting to them. It can also add line numbers\n * and handle HTML entity decoding.\n *\n * @param {Options} [options] - Configuration options\n * @param {boolean} [options.decode=false] - Whether to decode HTML entities in code blocks\n * @param {boolean} [options.lineNumbers=false] - Whether to add line numbers to code blocks\n * @param {string[]} [options.preLoad=[]] - Languages to preload before processing\n * @returns {import('metalsmith').Plugin} - A metalsmith plugin function\n * @example\n * // Basic usage\n * metalsmith.use(prism());\n *\n * // With options\n * metalsmith.use(prism({\n * decode: true,\n * lineNumbers: true,\n * preLoad: ['java', 'scala']\n * }));\n */\nconst metalsmithPrism = ( options = {} ) => {\n // Create a new options object with defaults\n const opts = {\n decode: false,\n lineNumbers: false,\n preLoad: [],\n ...options\n };\n\n // Track loaded languages to avoid duplicate loading\n const loadedLanguages = new Set();\n\n // Always load PHP by default\n debug( 'Loading PHP by default' );\n try {\n loadLanguages( [ 'php' ] );\n loadedLanguages.add( 'php' );\n } catch ( e ) {\n debug( 'Failed to load PHP:', e );\n }\n\n if ( opts.preLoad && opts.preLoad.length ) {\n debug( 'Preloading languages:', opts.preLoad );\n opts.preLoad.forEach( ( language ) => {\n if ( !loadedLanguages.has( language ) ) {\n try {\n loadLanguages( [ language ] );\n loadedLanguages.add( language );\n debug( `Successfully preloaded language: ${ language }` );\n } catch ( e ) {\n console.warn( `Failed to preload prism syntax: ${ language }!`, e );\n debug( `Error preloading language ${ language }:`, e );\n }\n } else {\n debug( `Language ${ language } already loaded, skipping` );\n }\n } );\n }\n\n /**\n * Require optional language package\n * @param {string} language\n * @param {Set} loadedLanguages\n */\n const requireLanguage = ( language, loadedLanguages ) => {\n if ( loadedLanguages.has( language ) || languages[ language ] ) {\n debug( `Language ${ language } already available, skipping load` );\n return;\n }\n\n debug( `Loading language on-demand: ${ language }` );\n try {\n loadLanguages( [ language ] );\n loadedLanguages.add( language );\n debug( `Successfully loaded language: ${ language }` );\n } catch ( e ) {\n console.warn( `Failed to load prism syntax: ${ language }!`, e );\n debug( `Error loading language ${ language }:`, e );\n }\n };\n\n // Set up line numbers functionality\n const NEW_LINE_EXP = /\\n(?!$)/g;\n let lineNumbersWrapper;\n\n // Only set up the hook if line numbers are requested\n if ( opts.lineNumbers ) {\n debug( 'Setting up line numbers hook' );\n Prism.hooks.add( 'after-tokenize', ( env ) => {\n const match = env.code.match( NEW_LINE_EXP );\n const linesNum = match ? match.length + 1 : 1;\n debug( `Counted ${ linesNum } lines for line numbers` );\n const lines = new Array( linesNum + 1 ).join( '<span></span>' );\n lineNumbersWrapper = `<span aria-hidden=\"true\" class=\"line-numbers-rows\">${ lines }</span>`;\n } );\n }\n\n return ( files, metalsmith, done ) => {\n debug( 'Starting metalsmith-prism plugin' );\n debug( 'Options:', opts );\n\n // Call done asynchronously to avoid blocking\n setImmediate( done );\n\n try {\n Object.keys( files ).forEach( ( file ) => {\n if ( !isHTMLFile( file ) ) {\n return;\n }\n\n debug( `Processing HTML file: ${ file }` );\n const contents = files[ file ].contents.toString();\n const $ = load( contents, { decodeEntities: false } );\n let highlighted = false;\n const code = $( 'code' );\n\n if ( !code.length ) {\n debug( `No code blocks found in ${ file }` );\n return;\n }\n\n debug( `Found ${ code.length } code blocks in ${ file }` );\n\n code.each( function() {\n const $this = $( this );\n\n const className = $this.attr( 'class' ) || '';\n const targets = className.split( 'language-' );\n let addLineNumbers = false;\n\n if ( targets.length > 1 ) {\n const $pre = $this.parent( 'pre' );\n\n if ( $pre ) {\n // Copy className to <pre> container\n $pre.addClass( className );\n\n if ( opts.lineNumbers ) {\n $pre.addClass( 'line-numbers' );\n addLineNumbers = true;\n debug( 'Adding line numbers' );\n }\n }\n\n highlighted = true;\n let language = targets[ 1 ];\n debug( `Detected language: ${ language }` );\n requireLanguage( language, loadedLanguages );\n\n if ( !languages[ language ] ) {\n debug( `Language ${ language } not available, falling back to markup` );\n language = 'markup';\n }\n\n const html = language === 'markup' && !opts.decode ? $this.html() : he.decode( $this.html() );\n debug( `HTML decoding ${ opts.decode ? 'applied' : 'not applied' } for language ${ language }` );\n\n debug( `Highlighting code with language: ${ language }` );\n const highlightedCode = Prism.highlight( html, languages[ language ] );\n $this.html( addLineNumbers ? highlightedCode + lineNumbersWrapper : highlightedCode );\n }\n } );\n\n if ( highlighted ) {\n debug( `Updating contents of ${ file } with highlighted code` );\n files[ file ].contents = Buffer.from( $.html() );\n } else {\n debug( `No code was highlighted in ${ file }` );\n }\n } );\n\n debug( 'Completed metalsmith-prism plugin' );\n } catch ( error ) {\n debug( 'Error in metalsmith-prism plugin:', error );\n // We can't call done(error) here because done has already been called\n console.error( 'Error processing files:', error );\n }\n };\n};\n\n// ESM export\nexport default metalsmithPrism;\n"],"names":["debug","debugLib","languages","Prism","isHTMLFile","filePath","extension","extname","toLowerCase","includes","metalsmithPrism","options","opts","decode","lineNumbers","preLoad","loadedLanguages","Set","loadLanguages","add","e","length","forEach","language","has","console","warn","requireLanguage","NEW_LINE_EXP","lineNumbersWrapper","hooks","env","match","code","linesNum","lines","Array","join","files","metalsmith","done","setImmediate","Object","keys","file","contents","toString","$","load","decodeEntities","highlighted","each","$this","className","attr","targets","split","addLineNumbers","$pre","parent","addClass","html","he","highlightedCode","highlight","Buffer","from","error"],"mappings":"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"}
package/lib/index.js CHANGED
@@ -3,20 +3,23 @@ import { extname } from 'path';
3
3
  import Prism from 'prismjs';
4
4
  import loadLanguages from 'prismjs/components/index.js';
5
5
  import he from 'he';
6
+ import debugLib from 'debug';
6
7
 
7
- // Import languages from Prism's default export
8
- const { languages } = Prism;
8
+ const debug = debugLib('metalsmith-prism');
9
9
 
10
- // Preload PHP
11
- loadLanguages( [ 'php' ] );
10
+ // Import languages from Prism's default export
11
+ const {
12
+ languages
13
+ } = Prism;
12
14
 
13
15
  /**
14
- * Check if a file is HTML
15
- * @param {string} filePath
16
- * @returns {boolean}
16
+ * Check if a file is HTML based on its extension
17
+ * @param {string} filePath - Path to the file
18
+ * @returns {boolean} - True if the file has an HTML extension
17
19
  */
18
- const isHTMLFile = ( filePath ) => {
19
- return /\.html|\.htm/.test( extname( filePath ) );
20
+ const isHTMLFile = filePath => {
21
+ const extension = extname(filePath).toLowerCase();
22
+ return ['.html', '.htm'].includes(extension);
20
23
  };
21
24
 
22
25
  /**
@@ -29,102 +32,169 @@ const isHTMLFile = ( filePath ) => {
29
32
  /**
30
33
  * Metalsmith plugin to highlight code syntax with PrismJS
31
34
  *
32
- * @param {Options} [options]
33
- * @returns {import('metalsmith').Plugin}
35
+ * This plugin finds all code blocks in HTML files that have language-* classes
36
+ * and applies Prism.js syntax highlighting to them. It can also add line numbers
37
+ * and handle HTML entity decoding.
38
+ *
39
+ * @param {Options} [options] - Configuration options
40
+ * @param {boolean} [options.decode=false] - Whether to decode HTML entities in code blocks
41
+ * @param {boolean} [options.lineNumbers=false] - Whether to add line numbers to code blocks
42
+ * @param {string[]} [options.preLoad=[]] - Languages to preload before processing
43
+ * @returns {import('metalsmith').Plugin} - A metalsmith plugin function
44
+ * @example
45
+ * // Basic usage
46
+ * metalsmith.use(prism());
47
+ *
48
+ * // With options
49
+ * metalsmith.use(prism({
50
+ * decode: true,
51
+ * lineNumbers: true,
52
+ * preLoad: ['java', 'scala']
53
+ * }));
34
54
  */
35
- function metalsmithPrism( options = {} ) {
36
- if ( options.preLoad ) {
37
- options.preLoad.forEach( ( language ) => {
38
- try {
39
- loadLanguages( [ language ] );
40
- } catch ( e ) {
41
- console.warn( `Failed to preload prism syntax: ${ language }!` );
55
+ const metalsmithPrism = (options = {}) => {
56
+ // Create a new options object with defaults
57
+ const opts = {
58
+ decode: false,
59
+ lineNumbers: false,
60
+ preLoad: [],
61
+ ...options
62
+ };
63
+
64
+ // Track loaded languages to avoid duplicate loading
65
+ const loadedLanguages = new Set();
66
+
67
+ // Always load PHP by default
68
+ debug('Loading PHP by default');
69
+ try {
70
+ loadLanguages(['php']);
71
+ loadedLanguages.add('php');
72
+ } catch (e) {
73
+ debug('Failed to load PHP:', e);
74
+ }
75
+ if (opts.preLoad && opts.preLoad.length) {
76
+ debug('Preloading languages:', opts.preLoad);
77
+ opts.preLoad.forEach(language => {
78
+ if (!loadedLanguages.has(language)) {
79
+ try {
80
+ loadLanguages([language]);
81
+ loadedLanguages.add(language);
82
+ debug(`Successfully preloaded language: ${language}`);
83
+ } catch (e) {
84
+ console.warn(`Failed to preload prism syntax: ${language}!`, e);
85
+ debug(`Error preloading language ${language}:`, e);
86
+ }
87
+ } else {
88
+ debug(`Language ${language} already loaded, skipping`);
42
89
  }
43
- } );
90
+ });
44
91
  }
45
92
 
46
93
  /**
47
94
  * Require optional language package
48
95
  * @param {string} language
96
+ * @param {Set} loadedLanguages
49
97
  */
50
- function requireLanguage( language ) {
51
- if ( !languages[ language ] ) {
52
- try {
53
- loadLanguages( [ language ] );
54
- } catch ( e ) {
55
- console.warn( `Failed to load prism syntax: ${ language }!` );
56
- }
98
+ const requireLanguage = (language, loadedLanguages) => {
99
+ if (loadedLanguages.has(language) || languages[language]) {
100
+ debug(`Language ${language} already available, skipping load`);
101
+ return;
57
102
  }
58
- }
103
+ debug(`Loading language on-demand: ${language}`);
104
+ try {
105
+ loadLanguages([language]);
106
+ loadedLanguages.add(language);
107
+ debug(`Successfully loaded language: ${language}`);
108
+ } catch (e) {
109
+ console.warn(`Failed to load prism syntax: ${language}!`, e);
110
+ debug(`Error loading language ${language}:`, e);
111
+ }
112
+ };
59
113
 
60
- // Set up line numbers
114
+ // Set up line numbers functionality
61
115
  const NEW_LINE_EXP = /\n(?!$)/g;
62
116
  let lineNumbersWrapper;
63
117
 
64
- Prism.hooks.add( 'after-tokenize', function( env ) {
65
- const match = env.code.match( NEW_LINE_EXP );
66
- const linesNum = match ? match.length + 1 : 1;
67
- const lines = new Array( linesNum + 1 ).join( '<span></span>' );
68
- lineNumbersWrapper = `<span aria-hidden="true" class="line-numbers-rows">${ lines }</span>`;
69
- } );
70
-
71
- return function( files, metalsmith, done ) {
72
- setImmediate( done );
73
-
74
- Object.keys( files ).forEach( file => {
75
- if ( !isHTMLFile( file ) ) {
76
- return;
77
- }
78
-
79
- const contents = files[ file ].contents.toString();
80
- const $ = load( contents, { decodeEntities: false } );
81
- let highlighted = false;
82
- const code = $( 'code' );
83
-
84
- if ( !code.length ) return;
85
-
86
- code.each( function() {
87
- const $this = $( this );
88
-
89
- const className = $this.attr( 'class' ) || '';
90
- const targets = className.split( 'language-' );
91
- let addLineNumbers = false;
92
-
93
- if ( targets.length > 1 ) {
94
- const $pre = $this.parent( 'pre' );
95
-
96
- if ( $pre ) {
97
- // Copy className to <pre> container
98
- $pre.addClass( className );
99
-
100
- if ( options.lineNumbers ) {
101
- $pre.addClass( 'line-numbers' );
102
- addLineNumbers = true;
118
+ // Only set up the hook if line numbers are requested
119
+ if (opts.lineNumbers) {
120
+ debug('Setting up line numbers hook');
121
+ Prism.hooks.add('after-tokenize', env => {
122
+ const match = env.code.match(NEW_LINE_EXP);
123
+ const linesNum = match ? match.length + 1 : 1;
124
+ debug(`Counted ${linesNum} lines for line numbers`);
125
+ const lines = new Array(linesNum + 1).join('<span></span>');
126
+ lineNumbersWrapper = `<span aria-hidden="true" class="line-numbers-rows">${lines}</span>`;
127
+ });
128
+ }
129
+ return (files, metalsmith, done) => {
130
+ debug('Starting metalsmith-prism plugin');
131
+ debug('Options:', opts);
132
+
133
+ // Call done asynchronously to avoid blocking
134
+ setImmediate(done);
135
+ try {
136
+ Object.keys(files).forEach(file => {
137
+ if (!isHTMLFile(file)) {
138
+ return;
139
+ }
140
+ debug(`Processing HTML file: ${file}`);
141
+ const contents = files[file].contents.toString();
142
+ const $ = load(contents, {
143
+ decodeEntities: false
144
+ });
145
+ let highlighted = false;
146
+ const code = $('code');
147
+ if (!code.length) {
148
+ debug(`No code blocks found in ${file}`);
149
+ return;
150
+ }
151
+ debug(`Found ${code.length} code blocks in ${file}`);
152
+ code.each(function () {
153
+ const $this = $(this);
154
+ const className = $this.attr('class') || '';
155
+ const targets = className.split('language-');
156
+ let addLineNumbers = false;
157
+ if (targets.length > 1) {
158
+ const $pre = $this.parent('pre');
159
+ if ($pre) {
160
+ // Copy className to <pre> container
161
+ $pre.addClass(className);
162
+ if (opts.lineNumbers) {
163
+ $pre.addClass('line-numbers');
164
+ addLineNumbers = true;
165
+ debug('Adding line numbers');
166
+ }
103
167
  }
168
+ highlighted = true;
169
+ let language = targets[1];
170
+ debug(`Detected language: ${language}`);
171
+ requireLanguage(language, loadedLanguages);
172
+ if (!languages[language]) {
173
+ debug(`Language ${language} not available, falling back to markup`);
174
+ language = 'markup';
175
+ }
176
+ const html = language === 'markup' && !opts.decode ? $this.html() : he.decode($this.html());
177
+ debug(`HTML decoding ${opts.decode ? 'applied' : 'not applied'} for language ${language}`);
178
+ debug(`Highlighting code with language: ${language}`);
179
+ const highlightedCode = Prism.highlight(html, languages[language]);
180
+ $this.html(addLineNumbers ? highlightedCode + lineNumbersWrapper : highlightedCode);
104
181
  }
105
-
106
- highlighted = true;
107
- let language = targets[ 1 ];
108
- requireLanguage( language );
109
-
110
- if ( !languages[ language ] ) {
111
- language = 'markup';
112
- }
113
-
114
- const html = ( language === 'markup' && !options.decode )
115
- ? $this.html()
116
- : he.decode( $this.html() );
117
-
118
- const highlightedCode = Prism.highlight( html, languages[ language ] );
119
- $this.html( addLineNumbers ? highlightedCode + lineNumbersWrapper : highlightedCode );
182
+ });
183
+ if (highlighted) {
184
+ debug(`Updating contents of ${file} with highlighted code`);
185
+ files[file].contents = Buffer.from($.html());
186
+ } else {
187
+ debug(`No code was highlighted in ${file}`);
120
188
  }
121
- } );
122
-
123
- if ( highlighted ) {
124
- files[ file ].contents = Buffer.from( $.html() );
125
- }
126
- } );
189
+ });
190
+ debug('Completed metalsmith-prism plugin');
191
+ } catch (error) {
192
+ debug('Error in metalsmith-prism plugin:', error);
193
+ // We can't call done(error) here because done has already been called
194
+ console.error('Error processing files:', error);
195
+ }
127
196
  };
128
- }
197
+ };
129
198
 
130
- export default metalsmithPrism;
199
+ export { metalsmithPrism as default };
200
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../src/index.js"],"sourcesContent":["import { load } from 'cheerio';\nimport { extname } from 'path';\nimport Prism from 'prismjs';\nimport loadLanguages from 'prismjs/components/index.js';\nimport he from 'he';\nimport debugLib from 'debug';\n\nconst debug = debugLib( 'metalsmith-prism' );\n\n// Import languages from Prism's default export\nconst { languages } = Prism;\n\n/**\n * Check if a file is HTML based on its extension\n * @param {string} filePath - Path to the file\n * @returns {boolean} - True if the file has an HTML extension\n */\nconst isHTMLFile = ( filePath ) => {\n const extension = extname( filePath ).toLowerCase();\n return [ '.html', '.htm' ].includes( extension );\n};\n\n/**\n * @typedef Options\n * @property {boolean} [decode=false] - Whether to decode HTML entities\n * @property {boolean} [lineNumbers=false] - Whether to add line numbers\n * @property {string[]} [preLoad=[]] - Languages to preload\n */\n\n/**\n * Metalsmith plugin to highlight code syntax with PrismJS\n *\n * This plugin finds all code blocks in HTML files that have language-* classes\n * and applies Prism.js syntax highlighting to them. It can also add line numbers\n * and handle HTML entity decoding.\n *\n * @param {Options} [options] - Configuration options\n * @param {boolean} [options.decode=false] - Whether to decode HTML entities in code blocks\n * @param {boolean} [options.lineNumbers=false] - Whether to add line numbers to code blocks\n * @param {string[]} [options.preLoad=[]] - Languages to preload before processing\n * @returns {import('metalsmith').Plugin} - A metalsmith plugin function\n * @example\n * // Basic usage\n * metalsmith.use(prism());\n *\n * // With options\n * metalsmith.use(prism({\n * decode: true,\n * lineNumbers: true,\n * preLoad: ['java', 'scala']\n * }));\n */\nconst metalsmithPrism = ( options = {} ) => {\n // Create a new options object with defaults\n const opts = {\n decode: false,\n lineNumbers: false,\n preLoad: [],\n ...options\n };\n\n // Track loaded languages to avoid duplicate loading\n const loadedLanguages = new Set();\n\n // Always load PHP by default\n debug( 'Loading PHP by default' );\n try {\n loadLanguages( [ 'php' ] );\n loadedLanguages.add( 'php' );\n } catch ( e ) {\n debug( 'Failed to load PHP:', e );\n }\n\n if ( opts.preLoad && opts.preLoad.length ) {\n debug( 'Preloading languages:', opts.preLoad );\n opts.preLoad.forEach( ( language ) => {\n if ( !loadedLanguages.has( language ) ) {\n try {\n loadLanguages( [ language ] );\n loadedLanguages.add( language );\n debug( `Successfully preloaded language: ${ language }` );\n } catch ( e ) {\n console.warn( `Failed to preload prism syntax: ${ language }!`, e );\n debug( `Error preloading language ${ language }:`, e );\n }\n } else {\n debug( `Language ${ language } already loaded, skipping` );\n }\n } );\n }\n\n /**\n * Require optional language package\n * @param {string} language\n * @param {Set} loadedLanguages\n */\n const requireLanguage = ( language, loadedLanguages ) => {\n if ( loadedLanguages.has( language ) || languages[ language ] ) {\n debug( `Language ${ language } already available, skipping load` );\n return;\n }\n\n debug( `Loading language on-demand: ${ language }` );\n try {\n loadLanguages( [ language ] );\n loadedLanguages.add( language );\n debug( `Successfully loaded language: ${ language }` );\n } catch ( e ) {\n console.warn( `Failed to load prism syntax: ${ language }!`, e );\n debug( `Error loading language ${ language }:`, e );\n }\n };\n\n // Set up line numbers functionality\n const NEW_LINE_EXP = /\\n(?!$)/g;\n let lineNumbersWrapper;\n\n // Only set up the hook if line numbers are requested\n if ( opts.lineNumbers ) {\n debug( 'Setting up line numbers hook' );\n Prism.hooks.add( 'after-tokenize', ( env ) => {\n const match = env.code.match( NEW_LINE_EXP );\n const linesNum = match ? match.length + 1 : 1;\n debug( `Counted ${ linesNum } lines for line numbers` );\n const lines = new Array( linesNum + 1 ).join( '<span></span>' );\n lineNumbersWrapper = `<span aria-hidden=\"true\" class=\"line-numbers-rows\">${ lines }</span>`;\n } );\n }\n\n return ( files, metalsmith, done ) => {\n debug( 'Starting metalsmith-prism plugin' );\n debug( 'Options:', opts );\n\n // Call done asynchronously to avoid blocking\n setImmediate( done );\n\n try {\n Object.keys( files ).forEach( ( file ) => {\n if ( !isHTMLFile( file ) ) {\n return;\n }\n\n debug( `Processing HTML file: ${ file }` );\n const contents = files[ file ].contents.toString();\n const $ = load( contents, { decodeEntities: false } );\n let highlighted = false;\n const code = $( 'code' );\n\n if ( !code.length ) {\n debug( `No code blocks found in ${ file }` );\n return;\n }\n\n debug( `Found ${ code.length } code blocks in ${ file }` );\n\n code.each( function() {\n const $this = $( this );\n\n const className = $this.attr( 'class' ) || '';\n const targets = className.split( 'language-' );\n let addLineNumbers = false;\n\n if ( targets.length > 1 ) {\n const $pre = $this.parent( 'pre' );\n\n if ( $pre ) {\n // Copy className to <pre> container\n $pre.addClass( className );\n\n if ( opts.lineNumbers ) {\n $pre.addClass( 'line-numbers' );\n addLineNumbers = true;\n debug( 'Adding line numbers' );\n }\n }\n\n highlighted = true;\n let language = targets[ 1 ];\n debug( `Detected language: ${ language }` );\n requireLanguage( language, loadedLanguages );\n\n if ( !languages[ language ] ) {\n debug( `Language ${ language } not available, falling back to markup` );\n language = 'markup';\n }\n\n const html = language === 'markup' && !opts.decode ? $this.html() : he.decode( $this.html() );\n debug( `HTML decoding ${ opts.decode ? 'applied' : 'not applied' } for language ${ language }` );\n\n debug( `Highlighting code with language: ${ language }` );\n const highlightedCode = Prism.highlight( html, languages[ language ] );\n $this.html( addLineNumbers ? highlightedCode + lineNumbersWrapper : highlightedCode );\n }\n } );\n\n if ( highlighted ) {\n debug( `Updating contents of ${ file } with highlighted code` );\n files[ file ].contents = Buffer.from( $.html() );\n } else {\n debug( `No code was highlighted in ${ file }` );\n }\n } );\n\n debug( 'Completed metalsmith-prism plugin' );\n } catch ( error ) {\n debug( 'Error in metalsmith-prism plugin:', error );\n // We can't call done(error) here because done has already been called\n console.error( 'Error processing files:', error );\n }\n };\n};\n\n// ESM export\nexport default metalsmithPrism;\n"],"names":["debug","debugLib","languages","Prism","isHTMLFile","filePath","extension","extname","toLowerCase","includes","metalsmithPrism","options","opts","decode","lineNumbers","preLoad","loadedLanguages","Set","loadLanguages","add","e","length","forEach","language","has","console","warn","requireLanguage","NEW_LINE_EXP","lineNumbersWrapper","hooks","env","match","code","linesNum","lines","Array","join","files","metalsmith","done","setImmediate","Object","keys","file","contents","toString","$","load","decodeEntities","highlighted","each","$this","className","attr","targets","split","addLineNumbers","$pre","parent","addClass","html","he","highlightedCode","highlight","Buffer","from","error"],"mappings":";;;;;;;AAOA,MAAMA,KAAK,GAAGC,QAAQ,CAAE,kBAAmB,CAAC,CAAA;;AAE5C;AACA,MAAM;AAAEC,EAAAA,SAAAA;AAAU,CAAC,GAAGC,KAAK,CAAA;;AAE3B;AACA;AACA;AACA;AACA;AACA,MAAMC,UAAU,GAAKC,QAAQ,IAAM;EACjC,MAAMC,SAAS,GAAGC,OAAO,CAAEF,QAAS,CAAC,CAACG,WAAW,EAAE,CAAA;EACnD,OAAO,CAAE,OAAO,EAAE,MAAM,CAAE,CAACC,QAAQ,CAAEH,SAAU,CAAC,CAAA;AAClD,CAAC,CAAA;;AAED;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAMI,eAAe,GAAGA,CAAEC,OAAO,GAAG,EAAE,KAAM;AAC1C;AACA,EAAA,MAAMC,IAAI,GAAG;AACXC,IAAAA,MAAM,EAAE,KAAK;AACbC,IAAAA,WAAW,EAAE,KAAK;AAClBC,IAAAA,OAAO,EAAE,EAAE;IACX,GAAGJ,OAAAA;GACJ,CAAA;;AAED;AACA,EAAA,MAAMK,eAAe,GAAG,IAAIC,GAAG,EAAE,CAAA;;AAEjC;EACAjB,KAAK,CAAE,wBAAyB,CAAC,CAAA;EACjC,IAAI;AACFkB,IAAAA,aAAa,CAAE,CAAE,KAAK,CAAG,CAAC,CAAA;AAC1BF,IAAAA,eAAe,CAACG,GAAG,CAAE,KAAM,CAAC,CAAA;GAC7B,CAAC,OAAQC,CAAC,EAAG;AACZpB,IAAAA,KAAK,CAAE,qBAAqB,EAAEoB,CAAE,CAAC,CAAA;AACnC,GAAA;EAEA,IAAKR,IAAI,CAACG,OAAO,IAAIH,IAAI,CAACG,OAAO,CAACM,MAAM,EAAG;AACzCrB,IAAAA,KAAK,CAAE,uBAAuB,EAAEY,IAAI,CAACG,OAAQ,CAAC,CAAA;AAC9CH,IAAAA,IAAI,CAACG,OAAO,CAACO,OAAO,CAAIC,QAAQ,IAAM;AACpC,MAAA,IAAK,CAACP,eAAe,CAACQ,GAAG,CAAED,QAAS,CAAC,EAAG;QACtC,IAAI;AACFL,UAAAA,aAAa,CAAE,CAAEK,QAAQ,CAAG,CAAC,CAAA;AAC7BP,UAAAA,eAAe,CAACG,GAAG,CAAEI,QAAS,CAAC,CAAA;AAC/BvB,UAAAA,KAAK,CAAE,CAAA,iCAAA,EAAqCuB,QAAQ,CAAA,CAAI,CAAC,CAAA;SAC1D,CAAC,OAAQH,CAAC,EAAG;UACZK,OAAO,CAACC,IAAI,CAAE,CAAA,gCAAA,EAAoCH,QAAQ,CAAI,CAAA,CAAA,EAAEH,CAAE,CAAC,CAAA;AACnEpB,UAAAA,KAAK,CAAE,CAA8BuB,0BAAAA,EAAAA,QAAQ,CAAI,CAAA,CAAA,EAAEH,CAAE,CAAC,CAAA;AACxD,SAAA;AACF,OAAC,MAAM;AACLpB,QAAAA,KAAK,CAAE,CAAA,SAAA,EAAauB,QAAQ,CAAA,yBAAA,CAA6B,CAAC,CAAA;AAC5D,OAAA;AACF,KAAE,CAAC,CAAA;AACL,GAAA;;AAEA;AACF;AACA;AACA;AACA;AACE,EAAA,MAAMI,eAAe,GAAGA,CAAEJ,QAAQ,EAAEP,eAAe,KAAM;IACvD,IAAKA,eAAe,CAACQ,GAAG,CAAED,QAAS,CAAC,IAAIrB,SAAS,CAAEqB,QAAQ,CAAE,EAAG;AAC9DvB,MAAAA,KAAK,CAAE,CAAA,SAAA,EAAauB,QAAQ,CAAA,iCAAA,CAAqC,CAAC,CAAA;AAClE,MAAA,OAAA;AACF,KAAA;AAEAvB,IAAAA,KAAK,CAAE,CAAA,4BAAA,EAAgCuB,QAAQ,CAAA,CAAI,CAAC,CAAA;IACpD,IAAI;AACFL,MAAAA,aAAa,CAAE,CAAEK,QAAQ,CAAG,CAAC,CAAA;AAC7BP,MAAAA,eAAe,CAACG,GAAG,CAAEI,QAAS,CAAC,CAAA;AAC/BvB,MAAAA,KAAK,CAAE,CAAA,8BAAA,EAAkCuB,QAAQ,CAAA,CAAI,CAAC,CAAA;KACvD,CAAC,OAAQH,CAAC,EAAG;MACZK,OAAO,CAACC,IAAI,CAAE,CAAA,6BAAA,EAAiCH,QAAQ,CAAI,CAAA,CAAA,EAAEH,CAAE,CAAC,CAAA;AAChEpB,MAAAA,KAAK,CAAE,CAA2BuB,uBAAAA,EAAAA,QAAQ,CAAI,CAAA,CAAA,EAAEH,CAAE,CAAC,CAAA;AACrD,KAAA;GACD,CAAA;;AAED;EACA,MAAMQ,YAAY,GAAG,UAAU,CAAA;AAC/B,EAAA,IAAIC,kBAAkB,CAAA;;AAEtB;EACA,IAAKjB,IAAI,CAACE,WAAW,EAAG;IACtBd,KAAK,CAAE,8BAA+B,CAAC,CAAA;IACvCG,KAAK,CAAC2B,KAAK,CAACX,GAAG,CAAE,gBAAgB,EAAIY,GAAG,IAAM;MAC5C,MAAMC,KAAK,GAAGD,GAAG,CAACE,IAAI,CAACD,KAAK,CAAEJ,YAAa,CAAC,CAAA;MAC5C,MAAMM,QAAQ,GAAGF,KAAK,GAAGA,KAAK,CAACX,MAAM,GAAG,CAAC,GAAG,CAAC,CAAA;AAC7CrB,MAAAA,KAAK,CAAE,CAAA,QAAA,EAAYkC,QAAQ,CAAA,uBAAA,CAA2B,CAAC,CAAA;AACvD,MAAA,MAAMC,KAAK,GAAG,IAAIC,KAAK,CAAEF,QAAQ,GAAG,CAAE,CAAC,CAACG,IAAI,CAAE,eAAgB,CAAC,CAAA;MAC/DR,kBAAkB,GAAG,CAAuDM,mDAAAA,EAAAA,KAAK,CAAU,OAAA,CAAA,CAAA;AAC7F,KAAE,CAAC,CAAA;AACL,GAAA;AAEA,EAAA,OAAO,CAAEG,KAAK,EAAEC,UAAU,EAAEC,IAAI,KAAM;IACpCxC,KAAK,CAAE,kCAAmC,CAAC,CAAA;AAC3CA,IAAAA,KAAK,CAAE,UAAU,EAAEY,IAAK,CAAC,CAAA;;AAEzB;IACA6B,YAAY,CAAED,IAAK,CAAC,CAAA;IAEpB,IAAI;MACFE,MAAM,CAACC,IAAI,CAAEL,KAAM,CAAC,CAAChB,OAAO,CAAIsB,IAAI,IAAM;AACxC,QAAA,IAAK,CAACxC,UAAU,CAAEwC,IAAK,CAAC,EAAG;AACzB,UAAA,OAAA;AACF,SAAA;AAEA5C,QAAAA,KAAK,CAAE,CAAA,sBAAA,EAA0B4C,IAAI,CAAA,CAAI,CAAC,CAAA;QAC1C,MAAMC,QAAQ,GAAGP,KAAK,CAAEM,IAAI,CAAE,CAACC,QAAQ,CAACC,QAAQ,EAAE,CAAA;AAClD,QAAA,MAAMC,CAAC,GAAGC,IAAI,CAAEH,QAAQ,EAAE;AAAEI,UAAAA,cAAc,EAAE,KAAA;AAAM,SAAE,CAAC,CAAA;QACrD,IAAIC,WAAW,GAAG,KAAK,CAAA;AACvB,QAAA,MAAMjB,IAAI,GAAGc,CAAC,CAAE,MAAO,CAAC,CAAA;AAExB,QAAA,IAAK,CAACd,IAAI,CAACZ,MAAM,EAAG;AAClBrB,UAAAA,KAAK,CAAE,CAAA,wBAAA,EAA4B4C,IAAI,CAAA,CAAI,CAAC,CAAA;AAC5C,UAAA,OAAA;AACF,SAAA;QAEA5C,KAAK,CAAE,SAAUiC,IAAI,CAACZ,MAAM,CAAqBuB,gBAAAA,EAAAA,IAAI,EAAI,CAAC,CAAA;QAE1DX,IAAI,CAACkB,IAAI,CAAE,YAAW;AACpB,UAAA,MAAMC,KAAK,GAAGL,CAAC,CAAE,IAAK,CAAC,CAAA;UAEvB,MAAMM,SAAS,GAAGD,KAAK,CAACE,IAAI,CAAE,OAAQ,CAAC,IAAI,EAAE,CAAA;AAC7C,UAAA,MAAMC,OAAO,GAAGF,SAAS,CAACG,KAAK,CAAE,WAAY,CAAC,CAAA;UAC9C,IAAIC,cAAc,GAAG,KAAK,CAAA;AAE1B,UAAA,IAAKF,OAAO,CAAClC,MAAM,GAAG,CAAC,EAAG;AACxB,YAAA,MAAMqC,IAAI,GAAGN,KAAK,CAACO,MAAM,CAAE,KAAM,CAAC,CAAA;AAElC,YAAA,IAAKD,IAAI,EAAG;AACV;AACAA,cAAAA,IAAI,CAACE,QAAQ,CAAEP,SAAU,CAAC,CAAA;cAE1B,IAAKzC,IAAI,CAACE,WAAW,EAAG;AACtB4C,gBAAAA,IAAI,CAACE,QAAQ,CAAE,cAAe,CAAC,CAAA;AAC/BH,gBAAAA,cAAc,GAAG,IAAI,CAAA;gBACrBzD,KAAK,CAAE,qBAAsB,CAAC,CAAA;AAChC,eAAA;AACF,aAAA;AAEAkD,YAAAA,WAAW,GAAG,IAAI,CAAA;AAClB,YAAA,IAAI3B,QAAQ,GAAGgC,OAAO,CAAE,CAAC,CAAE,CAAA;AAC3BvD,YAAAA,KAAK,CAAE,CAAA,mBAAA,EAAuBuB,QAAQ,CAAA,CAAI,CAAC,CAAA;AAC3CI,YAAAA,eAAe,CAAEJ,QAAQ,EAAEP,eAAgB,CAAC,CAAA;AAE5C,YAAA,IAAK,CAACd,SAAS,CAAEqB,QAAQ,CAAE,EAAG;AAC5BvB,cAAAA,KAAK,CAAE,CAAA,SAAA,EAAauB,QAAQ,CAAA,sCAAA,CAA0C,CAAC,CAAA;AACvEA,cAAAA,QAAQ,GAAG,QAAQ,CAAA;AACrB,aAAA;YAEA,MAAMsC,IAAI,GAAGtC,QAAQ,KAAK,QAAQ,IAAI,CAACX,IAAI,CAACC,MAAM,GAAGuC,KAAK,CAACS,IAAI,EAAE,GAAGC,EAAE,CAACjD,MAAM,CAAEuC,KAAK,CAACS,IAAI,EAAG,CAAC,CAAA;AAC7F7D,YAAAA,KAAK,CAAE,CAAA,cAAA,EAAkBY,IAAI,CAACC,MAAM,GAAG,SAAS,GAAG,aAAa,CAAA,cAAA,EAAmBU,QAAQ,CAAA,CAAI,CAAC,CAAA;AAEhGvB,YAAAA,KAAK,CAAE,CAAA,iCAAA,EAAqCuB,QAAQ,CAAA,CAAI,CAAC,CAAA;AACzD,YAAA,MAAMwC,eAAe,GAAG5D,KAAK,CAAC6D,SAAS,CAAEH,IAAI,EAAE3D,SAAS,CAAEqB,QAAQ,CAAG,CAAC,CAAA;YACtE6B,KAAK,CAACS,IAAI,CAAEJ,cAAc,GAAGM,eAAe,GAAGlC,kBAAkB,GAAGkC,eAAgB,CAAC,CAAA;AACvF,WAAA;AACF,SAAE,CAAC,CAAA;AAEH,QAAA,IAAKb,WAAW,EAAG;AACjBlD,UAAAA,KAAK,CAAE,CAAA,qBAAA,EAAyB4C,IAAI,CAAA,sBAAA,CAA0B,CAAC,CAAA;AAC/DN,UAAAA,KAAK,CAAEM,IAAI,CAAE,CAACC,QAAQ,GAAGoB,MAAM,CAACC,IAAI,CAAEnB,CAAC,CAACc,IAAI,EAAG,CAAC,CAAA;AAClD,SAAC,MAAM;AACL7D,UAAAA,KAAK,CAAE,CAAA,2BAAA,EAA+B4C,IAAI,CAAA,CAAI,CAAC,CAAA;AACjD,SAAA;AACF,OAAE,CAAC,CAAA;MAEH5C,KAAK,CAAE,mCAAoC,CAAC,CAAA;KAC7C,CAAC,OAAQmE,KAAK,EAAG;AAChBnE,MAAAA,KAAK,CAAE,mCAAmC,EAAEmE,KAAM,CAAC,CAAA;AACnD;AACA1C,MAAAA,OAAO,CAAC0C,KAAK,CAAE,yBAAyB,EAAEA,KAAM,CAAC,CAAA;AACnD,KAAA;GACD,CAAA;AACH;;;;"}
@@ -0,0 +1,186 @@
1
+ import { load } from 'cheerio';
2
+ import { extname } from 'path';
3
+ import Prism from 'prismjs';
4
+ import loadLanguages from 'prismjs/components/index.js';
5
+ import he from 'he';
6
+ import debugLib from 'debug';
7
+
8
+ const debug = debugLib('metalsmith-prism');
9
+
10
+ // Import languages from Prism's default export
11
+ const {
12
+ languages
13
+ } = Prism;
14
+
15
+ /**
16
+ * Check if a file is HTML based on its extension
17
+ * @param {string} filePath - Path to the file
18
+ * @returns {boolean} - True if the file has an HTML extension
19
+ */
20
+ const isHTMLFile = filePath => {
21
+ const extension = extname(filePath).toLowerCase();
22
+ return extension === '.html' || extension === '.htm';
23
+ };
24
+
25
+ /**
26
+ * @typedef Options
27
+ * @property {boolean} [decode=false] - Whether to decode HTML entities
28
+ * @property {boolean} [lineNumbers=false] - Whether to add line numbers
29
+ * @property {string[]} [preLoad=[]] - Languages to preload
30
+ */
31
+
32
+ /**
33
+ * Metalsmith plugin to highlight code syntax with PrismJS
34
+ *
35
+ * This plugin finds all code blocks in HTML files that have language-* classes
36
+ * and applies Prism.js syntax highlighting to them. It can also add line numbers
37
+ * and handle HTML entity decoding.
38
+ *
39
+ * @param {Options} [options] - Configuration options
40
+ * @param {boolean} [options.decode=false] - Whether to decode HTML entities in code blocks
41
+ * @param {boolean} [options.lineNumbers=false] - Whether to add line numbers to code blocks
42
+ * @param {string[]} [options.preLoad=[]] - Languages to preload before processing
43
+ * @returns {import('metalsmith').Plugin} - A metalsmith plugin function
44
+ * @example
45
+ * // Basic usage
46
+ * metalsmith.use(prism());
47
+ *
48
+ * // With options
49
+ * metalsmith.use(prism({
50
+ * decode: true,
51
+ * lineNumbers: true,
52
+ * preLoad: ['java', 'scala']
53
+ * }));
54
+ */
55
+ function metalsmithPrism(options = {}) {
56
+ // Track loaded languages to avoid duplicate loading
57
+ const loadedLanguages = new Set();
58
+
59
+ // Always load PHP by default
60
+ debug('Loading PHP by default');
61
+ try {
62
+ loadLanguages(['php']);
63
+ loadedLanguages.add('php');
64
+ } catch (e) {
65
+ debug('Failed to load PHP:', e);
66
+ }
67
+ if (options.preLoad) {
68
+ debug('Preloading languages:', options.preLoad);
69
+ options.preLoad.forEach(language => {
70
+ if (!loadedLanguages.has(language)) {
71
+ try {
72
+ loadLanguages([language]);
73
+ loadedLanguages.add(language);
74
+ debug(`Successfully preloaded language: ${language}`);
75
+ } catch (e) {
76
+ console.warn(`Failed to preload prism syntax: ${language}!`, e);
77
+ debug(`Error preloading language ${language}:`, e);
78
+ }
79
+ } else {
80
+ debug(`Language ${language} already loaded, skipping`);
81
+ }
82
+ });
83
+ }
84
+
85
+ /**
86
+ * Require optional language package
87
+ * @param {string} language
88
+ */
89
+ function requireLanguage(language) {
90
+ if (!loadedLanguages.has(language) && !languages[language]) {
91
+ debug(`Loading language on-demand: ${language}`);
92
+ try {
93
+ loadLanguages([language]);
94
+ loadedLanguages.add(language);
95
+ debug(`Successfully loaded language: ${language}`);
96
+ } catch (e) {
97
+ console.warn(`Failed to load prism syntax: ${language}!`, e);
98
+ debug(`Error loading language ${language}:`, e);
99
+ }
100
+ }
101
+ }
102
+
103
+ // Set up line numbers functionality
104
+ const NEW_LINE_EXP = /\n(?!$)/g;
105
+ let lineNumbersWrapper;
106
+
107
+ // Only set up the hook if line numbers are requested
108
+ if (options.lineNumbers) {
109
+ debug('Setting up line numbers hook');
110
+ Prism.hooks.add('after-tokenize', env => {
111
+ const match = env.code.match(NEW_LINE_EXP);
112
+ const linesNum = match ? match.length + 1 : 1;
113
+ debug(`Counted ${linesNum} lines for line numbers`);
114
+ const lines = new Array(linesNum + 1).join('<span></span>');
115
+ lineNumbersWrapper = `<span aria-hidden="true" class="line-numbers-rows">${lines}</span>`;
116
+ });
117
+ }
118
+ return function (files, metalsmith, done) {
119
+ debug('Starting metalsmith-prism plugin');
120
+ debug('Options:', options);
121
+ setImmediate(done);
122
+ Object.keys(files).forEach(file => {
123
+ if (!isHTMLFile(file)) {
124
+ return;
125
+ }
126
+ debug(`Processing HTML file: ${file}`);
127
+ const contents = files[file].contents.toString();
128
+ const $ = load(contents, {
129
+ decodeEntities: false
130
+ });
131
+ let highlighted = false;
132
+ const code = $('code');
133
+ if (!code.length) {
134
+ debug(`No code blocks found in ${file}`);
135
+ return;
136
+ }
137
+ debug(`Found ${code.length} code blocks in ${file}`);
138
+ code.each(function () {
139
+ const $this = $(this);
140
+ const className = $this.attr('class') || '';
141
+ const targets = className.split('language-');
142
+ let addLineNumbers = false;
143
+ if (targets.length > 1) {
144
+ const $pre = $this.parent('pre');
145
+ if ($pre) {
146
+ // Copy className to <pre> container
147
+ $pre.addClass(className);
148
+ if (options.lineNumbers) {
149
+ $pre.addClass('line-numbers');
150
+ addLineNumbers = true;
151
+ debug('Adding line numbers');
152
+ }
153
+ }
154
+ highlighted = true;
155
+ let language = targets[1];
156
+ debug(`Detected language: ${language}`);
157
+ requireLanguage(language);
158
+ if (!languages[language]) {
159
+ debug(`Language ${language} not available, falling back to markup`);
160
+ language = 'markup';
161
+ }
162
+ const html = language === 'markup' && !options.decode ? $this.html() : he.decode($this.html());
163
+ debug(`HTML decoding ${options.decode ? 'applied' : 'not applied'} for language ${language}`);
164
+ debug(`Highlighting code with language: ${language}`);
165
+ const highlightedCode = Prism.highlight(html, languages[language]);
166
+ $this.html(addLineNumbers ? highlightedCode + lineNumbersWrapper : highlightedCode);
167
+ }
168
+ });
169
+ if (highlighted) {
170
+ debug(`Updating contents of ${file} with highlighted code`);
171
+ files[file].contents = Buffer.from($.html());
172
+ } else {
173
+ debug(`No code was highlighted in ${file}`);
174
+ }
175
+ });
176
+ debug('Completed metalsmith-prism plugin');
177
+ };
178
+ }
179
+
180
+ // CommonJS export compatibility
181
+ if (typeof module !== 'undefined') {
182
+ module.exports = metalsmithPrism;
183
+ }
184
+
185
+ export { metalsmithPrism as default };
186
+ //# sourceMappingURL=index.modern.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.modern.js","sources":["../src/index.js"],"sourcesContent":["import { load } from 'cheerio';\nimport { extname } from 'path';\nimport Prism from 'prismjs';\nimport loadLanguages from 'prismjs/components/index.js';\nimport he from 'he';\nimport debugLib from 'debug';\n\nconst debug = debugLib('metalsmith-prism');\n\n// Import languages from Prism's default export\nconst { languages } = Prism;\n\n/**\n * Check if a file is HTML based on its extension\n * @param {string} filePath - Path to the file\n * @returns {boolean} - True if the file has an HTML extension\n */\nconst isHTMLFile = (filePath) => {\n const extension = extname(filePath).toLowerCase();\n return extension === '.html' || extension === '.htm';\n};\n\n/**\n * @typedef Options\n * @property {boolean} [decode=false] - Whether to decode HTML entities\n * @property {boolean} [lineNumbers=false] - Whether to add line numbers\n * @property {string[]} [preLoad=[]] - Languages to preload\n */\n\n/**\n * Metalsmith plugin to highlight code syntax with PrismJS\n *\n * This plugin finds all code blocks in HTML files that have language-* classes\n * and applies Prism.js syntax highlighting to them. It can also add line numbers\n * and handle HTML entity decoding.\n *\n * @param {Options} [options] - Configuration options\n * @param {boolean} [options.decode=false] - Whether to decode HTML entities in code blocks\n * @param {boolean} [options.lineNumbers=false] - Whether to add line numbers to code blocks\n * @param {string[]} [options.preLoad=[]] - Languages to preload before processing\n * @returns {import('metalsmith').Plugin} - A metalsmith plugin function\n * @example\n * // Basic usage\n * metalsmith.use(prism());\n *\n * // With options\n * metalsmith.use(prism({\n * decode: true,\n * lineNumbers: true,\n * preLoad: ['java', 'scala']\n * }));\n */\nfunction metalsmithPrism(options = {}) {\n // Track loaded languages to avoid duplicate loading\n const loadedLanguages = new Set();\n\n // Always load PHP by default\n debug('Loading PHP by default');\n try {\n loadLanguages(['php']);\n loadedLanguages.add('php');\n } catch (e) {\n debug('Failed to load PHP:', e);\n }\n\n if (options.preLoad) {\n debug('Preloading languages:', options.preLoad);\n options.preLoad.forEach((language) => {\n if (!loadedLanguages.has(language)) {\n try {\n loadLanguages([language]);\n loadedLanguages.add(language);\n debug(`Successfully preloaded language: ${language}`);\n } catch (e) {\n console.warn(`Failed to preload prism syntax: ${language}!`, e);\n debug(`Error preloading language ${language}:`, e);\n }\n } else {\n debug(`Language ${language} already loaded, skipping`);\n }\n });\n }\n\n /**\n * Require optional language package\n * @param {string} language\n */\n function requireLanguage(language) {\n if (!loadedLanguages.has(language) && !languages[language]) {\n debug(`Loading language on-demand: ${language}`);\n try {\n loadLanguages([language]);\n loadedLanguages.add(language);\n debug(`Successfully loaded language: ${language}`);\n } catch (e) {\n console.warn(`Failed to load prism syntax: ${language}!`, e);\n debug(`Error loading language ${language}:`, e);\n }\n }\n }\n\n // Set up line numbers functionality\n const NEW_LINE_EXP = /\\n(?!$)/g;\n let lineNumbersWrapper;\n\n // Only set up the hook if line numbers are requested\n if (options.lineNumbers) {\n debug('Setting up line numbers hook');\n Prism.hooks.add('after-tokenize', (env) => {\n const match = env.code.match(NEW_LINE_EXP);\n const linesNum = match ? match.length + 1 : 1;\n debug(`Counted ${linesNum} lines for line numbers`);\n const lines = new Array(linesNum + 1).join('<span></span>');\n lineNumbersWrapper = `<span aria-hidden=\"true\" class=\"line-numbers-rows\">${lines}</span>`;\n });\n }\n\n return function (files, metalsmith, done) {\n debug('Starting metalsmith-prism plugin');\n debug('Options:', options);\n\n setImmediate(done);\n\n Object.keys(files).forEach((file) => {\n if (!isHTMLFile(file)) {\n return;\n }\n\n debug(`Processing HTML file: ${file}`);\n const contents = files[file].contents.toString();\n const $ = load(contents, { decodeEntities: false });\n let highlighted = false;\n const code = $('code');\n\n if (!code.length) {\n debug(`No code blocks found in ${file}`);\n return;\n }\n\n debug(`Found ${code.length} code blocks in ${file}`);\n\n code.each(function () {\n const $this = $(this);\n\n const className = $this.attr('class') || '';\n const targets = className.split('language-');\n let addLineNumbers = false;\n\n if (targets.length > 1) {\n const $pre = $this.parent('pre');\n\n if ($pre) {\n // Copy className to <pre> container\n $pre.addClass(className);\n\n if (options.lineNumbers) {\n $pre.addClass('line-numbers');\n addLineNumbers = true;\n debug('Adding line numbers');\n }\n }\n\n highlighted = true;\n let language = targets[1];\n debug(`Detected language: ${language}`);\n requireLanguage(language);\n\n if (!languages[language]) {\n debug(`Language ${language} not available, falling back to markup`);\n language = 'markup';\n }\n\n const html = language === 'markup' && !options.decode ? $this.html() : he.decode($this.html());\n debug(`HTML decoding ${options.decode ? 'applied' : 'not applied'} for language ${language}`);\n\n debug(`Highlighting code with language: ${language}`);\n const highlightedCode = Prism.highlight(html, languages[language]);\n $this.html(addLineNumbers ? highlightedCode + lineNumbersWrapper : highlightedCode);\n }\n });\n\n if (highlighted) {\n debug(`Updating contents of ${file} with highlighted code`);\n files[file].contents = Buffer.from($.html());\n } else {\n debug(`No code was highlighted in ${file}`);\n }\n });\n\n debug('Completed metalsmith-prism plugin');\n };\n}\n\n// ESM export\nexport { metalsmithPrism as default };\n\n// CommonJS export compatibility\nif (typeof module !== 'undefined') {\n module.exports = metalsmithPrism;\n}\n"],"names":["debug","debugLib","languages","Prism","isHTMLFile","filePath","extension","extname","toLowerCase","metalsmithPrism","options","loadedLanguages","Set","loadLanguages","add","e","preLoad","forEach","language","has","console","warn","requireLanguage","NEW_LINE_EXP","lineNumbersWrapper","lineNumbers","hooks","env","match","code","linesNum","length","lines","Array","join","files","metalsmith","done","setImmediate","Object","keys","file","contents","toString","$","load","decodeEntities","highlighted","each","$this","className","attr","targets","split","addLineNumbers","$pre","parent","addClass","html","decode","he","highlightedCode","highlight","Buffer","from","module","exports"],"mappings":"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"}
package/package.json CHANGED
@@ -1,17 +1,37 @@
1
1
  {
2
2
  "name": "metalsmith-prism",
3
- "version": "5.0.1",
3
+ "version": "5.0.4",
4
4
  "description": "Syntax highlighting for Metalsmith HTML templates using Prism.js",
5
5
  "type": "module",
6
- "main": "lib/index.js",
7
- "exports": "./lib/index.js",
6
+ "main": "./lib/index.cjs",
7
+ "module": "./lib/index.js",
8
+ "exports": {
9
+ "import": "./lib/index.js",
10
+ "require": "./lib/index.cjs",
11
+ "default": "./lib/index.js"
12
+ },
8
13
  "engines": {
9
14
  "node": ">= 18.0.0"
10
15
  },
11
16
  "scripts": {
12
- "preversion": "npm run test",
13
- "test": "mocha ./tests/index.mjs",
14
- "lint": "eslint ./lib ./tests"
17
+ "build": "microbundle --entry src/index.js --output lib/index.js --target node -f esm,cjs --strict --generateTypes=false",
18
+ "changelog": "auto-changelog -u --commit-limit false --ignore-commit-pattern '^((dev|chore|ci):|Release)'",
19
+ "coverage": "npm test && c8 report --reporter=text-lcov > ./coverage.info",
20
+ "format": "prettier --write \"**/*.{yml,md,json}\"",
21
+ "format:check": "prettier --list-different \"**/*.{yml,md,js,json}\"",
22
+ "lint": "eslint --fix .",
23
+ "lint:check": "eslint --fix-dry-run .",
24
+ "format-and-lint": "npm run format && npm run lint",
25
+ "prepublishOnly": "npm run build",
26
+ "update-coverage": "node scripts/update-coverage-badge.js",
27
+ "prerelease": "npm run update-coverage && git add README.md && git commit -m \"Update coverage badge in README\" || true",
28
+ "release": "npm run build && GITHUB_TOKEN=$(grep GITHUB_TOKEN .env | cut -d '=' -f2) ./node_modules/.bin/release-it . ",
29
+ "release:check": "npm run lint:check && npm run build && GITHUB_TOKEN=$(grep GITHUB_TOKEN .env | cut -d '=' -f2) ./node_modules/.bin/release-it . --dry-run",
30
+ "test": "c8 --include=src/**/*.js mocha 'test/index.js' 'test/cjs.test.cjs' 'test/comprehensive.mjs' 'test/additional.mjs' -t 15000",
31
+ "test:esm": "c8 --include=src/**/*.js mocha 'test/index.js' 'test/comprehensive.mjs' 'test/additional.mjs' -t 15000",
32
+ "test:cjs": "c8 --include=src/**/*.js mocha test/cjs.test.cjs -t 15000",
33
+ "test:e2e": "serve -l 3000 test/fixtures",
34
+ "depcheck": "depcheck"
15
35
  },
16
36
  "repository": {
17
37
  "type": "git",
@@ -24,6 +44,7 @@
24
44
  "highlighting"
25
45
  ],
26
46
  "files": [
47
+ "src",
27
48
  "lib",
28
49
  "README.md"
29
50
  ],
@@ -35,17 +56,23 @@
35
56
  "homepage": "https://github.com/wernerglinka/metalsmith-prism",
36
57
  "dependencies": {
37
58
  "cheerio": "^1.0.0",
38
- "debug": "^4.3.7",
59
+ "debug": "^4.4.0",
60
+ "dotenv": "^16.5.0",
39
61
  "he": "^1.2.0",
40
62
  "metalsmith": "^2.6.3",
41
- "prismjs": "^1.29.0"
63
+ "prismjs": "^1.30.0"
42
64
  },
43
65
  "devDependencies": {
44
- "chai": "^5.1.2",
45
- "eslint": "^9.15.0",
46
- "eslint-config-prettier": "^9.1.0",
47
- "eslint-plugin-prettier": "^5.2.1",
48
- "mocha": "^10.8.2",
49
- "prettier": "^3.3.3"
66
+ "auto-changelog": "^2.5.0",
67
+ "c8": "^10.1.3",
68
+ "chai": "^5.2.0",
69
+ "eslint": "^9.25.1",
70
+ "eslint-config-prettier": "^10.1.2",
71
+ "eslint-plugin-prettier": "^5.2.6",
72
+ "globals": "^16.0.0",
73
+ "microbundle": "^0.15.1",
74
+ "mocha": "^11.1.0",
75
+ "prettier": "^3.5.3",
76
+ "release-it": "^19.0.1"
50
77
  }
51
78
  }
package/src/index.js ADDED
@@ -0,0 +1,214 @@
1
+ import { load } from 'cheerio';
2
+ import { extname } from 'path';
3
+ import Prism from 'prismjs';
4
+ import loadLanguages from 'prismjs/components/index.js';
5
+ import he from 'he';
6
+ import debugLib from 'debug';
7
+
8
+ const debug = debugLib( 'metalsmith-prism' );
9
+
10
+ // Import languages from Prism's default export
11
+ const { languages } = Prism;
12
+
13
+ /**
14
+ * Check if a file is HTML based on its extension
15
+ * @param {string} filePath - Path to the file
16
+ * @returns {boolean} - True if the file has an HTML extension
17
+ */
18
+ const isHTMLFile = ( filePath ) => {
19
+ const extension = extname( filePath ).toLowerCase();
20
+ return [ '.html', '.htm' ].includes( extension );
21
+ };
22
+
23
+ /**
24
+ * @typedef Options
25
+ * @property {boolean} [decode=false] - Whether to decode HTML entities
26
+ * @property {boolean} [lineNumbers=false] - Whether to add line numbers
27
+ * @property {string[]} [preLoad=[]] - Languages to preload
28
+ */
29
+
30
+ /**
31
+ * Metalsmith plugin to highlight code syntax with PrismJS
32
+ *
33
+ * This plugin finds all code blocks in HTML files that have language-* classes
34
+ * and applies Prism.js syntax highlighting to them. It can also add line numbers
35
+ * and handle HTML entity decoding.
36
+ *
37
+ * @param {Options} [options] - Configuration options
38
+ * @param {boolean} [options.decode=false] - Whether to decode HTML entities in code blocks
39
+ * @param {boolean} [options.lineNumbers=false] - Whether to add line numbers to code blocks
40
+ * @param {string[]} [options.preLoad=[]] - Languages to preload before processing
41
+ * @returns {import('metalsmith').Plugin} - A metalsmith plugin function
42
+ * @example
43
+ * // Basic usage
44
+ * metalsmith.use(prism());
45
+ *
46
+ * // With options
47
+ * metalsmith.use(prism({
48
+ * decode: true,
49
+ * lineNumbers: true,
50
+ * preLoad: ['java', 'scala']
51
+ * }));
52
+ */
53
+ const metalsmithPrism = ( options = {} ) => {
54
+ // Create a new options object with defaults
55
+ const opts = {
56
+ decode: false,
57
+ lineNumbers: false,
58
+ preLoad: [],
59
+ ...options
60
+ };
61
+
62
+ // Track loaded languages to avoid duplicate loading
63
+ const loadedLanguages = new Set();
64
+
65
+ // Always load PHP by default
66
+ debug( 'Loading PHP by default' );
67
+ try {
68
+ loadLanguages( [ 'php' ] );
69
+ loadedLanguages.add( 'php' );
70
+ } catch ( e ) {
71
+ debug( 'Failed to load PHP:', e );
72
+ }
73
+
74
+ if ( opts.preLoad && opts.preLoad.length ) {
75
+ debug( 'Preloading languages:', opts.preLoad );
76
+ opts.preLoad.forEach( ( language ) => {
77
+ if ( !loadedLanguages.has( language ) ) {
78
+ try {
79
+ loadLanguages( [ language ] );
80
+ loadedLanguages.add( language );
81
+ debug( `Successfully preloaded language: ${ language }` );
82
+ } catch ( e ) {
83
+ console.warn( `Failed to preload prism syntax: ${ language }!`, e );
84
+ debug( `Error preloading language ${ language }:`, e );
85
+ }
86
+ } else {
87
+ debug( `Language ${ language } already loaded, skipping` );
88
+ }
89
+ } );
90
+ }
91
+
92
+ /**
93
+ * Require optional language package
94
+ * @param {string} language
95
+ * @param {Set} loadedLanguages
96
+ */
97
+ const requireLanguage = ( language, loadedLanguages ) => {
98
+ if ( loadedLanguages.has( language ) || languages[ language ] ) {
99
+ debug( `Language ${ language } already available, skipping load` );
100
+ return;
101
+ }
102
+
103
+ debug( `Loading language on-demand: ${ language }` );
104
+ try {
105
+ loadLanguages( [ language ] );
106
+ loadedLanguages.add( language );
107
+ debug( `Successfully loaded language: ${ language }` );
108
+ } catch ( e ) {
109
+ console.warn( `Failed to load prism syntax: ${ language }!`, e );
110
+ debug( `Error loading language ${ language }:`, e );
111
+ }
112
+ };
113
+
114
+ // Set up line numbers functionality
115
+ const NEW_LINE_EXP = /\n(?!$)/g;
116
+ let lineNumbersWrapper;
117
+
118
+ // Only set up the hook if line numbers are requested
119
+ if ( opts.lineNumbers ) {
120
+ debug( 'Setting up line numbers hook' );
121
+ Prism.hooks.add( 'after-tokenize', ( env ) => {
122
+ const match = env.code.match( NEW_LINE_EXP );
123
+ const linesNum = match ? match.length + 1 : 1;
124
+ debug( `Counted ${ linesNum } lines for line numbers` );
125
+ const lines = new Array( linesNum + 1 ).join( '<span></span>' );
126
+ lineNumbersWrapper = `<span aria-hidden="true" class="line-numbers-rows">${ lines }</span>`;
127
+ } );
128
+ }
129
+
130
+ return ( files, metalsmith, done ) => {
131
+ debug( 'Starting metalsmith-prism plugin' );
132
+ debug( 'Options:', opts );
133
+
134
+ // Call done asynchronously to avoid blocking
135
+ setImmediate( done );
136
+
137
+ try {
138
+ Object.keys( files ).forEach( ( file ) => {
139
+ if ( !isHTMLFile( file ) ) {
140
+ return;
141
+ }
142
+
143
+ debug( `Processing HTML file: ${ file }` );
144
+ const contents = files[ file ].contents.toString();
145
+ const $ = load( contents, { decodeEntities: false } );
146
+ let highlighted = false;
147
+ const code = $( 'code' );
148
+
149
+ if ( !code.length ) {
150
+ debug( `No code blocks found in ${ file }` );
151
+ return;
152
+ }
153
+
154
+ debug( `Found ${ code.length } code blocks in ${ file }` );
155
+
156
+ code.each( function() {
157
+ const $this = $( this );
158
+
159
+ const className = $this.attr( 'class' ) || '';
160
+ const targets = className.split( 'language-' );
161
+ let addLineNumbers = false;
162
+
163
+ if ( targets.length > 1 ) {
164
+ const $pre = $this.parent( 'pre' );
165
+
166
+ if ( $pre ) {
167
+ // Copy className to <pre> container
168
+ $pre.addClass( className );
169
+
170
+ if ( opts.lineNumbers ) {
171
+ $pre.addClass( 'line-numbers' );
172
+ addLineNumbers = true;
173
+ debug( 'Adding line numbers' );
174
+ }
175
+ }
176
+
177
+ highlighted = true;
178
+ let language = targets[ 1 ];
179
+ debug( `Detected language: ${ language }` );
180
+ requireLanguage( language, loadedLanguages );
181
+
182
+ if ( !languages[ language ] ) {
183
+ debug( `Language ${ language } not available, falling back to markup` );
184
+ language = 'markup';
185
+ }
186
+
187
+ const html = language === 'markup' && !opts.decode ? $this.html() : he.decode( $this.html() );
188
+ debug( `HTML decoding ${ opts.decode ? 'applied' : 'not applied' } for language ${ language }` );
189
+
190
+ debug( `Highlighting code with language: ${ language }` );
191
+ const highlightedCode = Prism.highlight( html, languages[ language ] );
192
+ $this.html( addLineNumbers ? highlightedCode + lineNumbersWrapper : highlightedCode );
193
+ }
194
+ } );
195
+
196
+ if ( highlighted ) {
197
+ debug( `Updating contents of ${ file } with highlighted code` );
198
+ files[ file ].contents = Buffer.from( $.html() );
199
+ } else {
200
+ debug( `No code was highlighted in ${ file }` );
201
+ }
202
+ } );
203
+
204
+ debug( 'Completed metalsmith-prism plugin' );
205
+ } catch ( error ) {
206
+ debug( 'Error in metalsmith-prism plugin:', error );
207
+ // We can't call done(error) here because done has already been called
208
+ console.error( 'Error processing files:', error );
209
+ }
210
+ };
211
+ };
212
+
213
+ // ESM export
214
+ export default metalsmithPrism;