@localnerve/web-component-build 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE.md ADDED
@@ -0,0 +1,11 @@
1
+ Copyright 2023 Alex Grant, LocalNerve, LLC
2
+
3
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
4
+
5
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
6
+
7
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8
+
9
+ 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
10
+
11
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package/README.md ADDED
@@ -0,0 +1,106 @@
1
+ # Web Component Build
2
+
3
+ > Assembles a web component from css, html, and js parts
4
+
5
+ This library assembles a web component from its parts, allowing developers to author the component's parts in separate files. The parts are processed and written to an output directory. After processing, this library exposes the parts to a calling build process.
6
+
7
+ The following is a table of _some_ of the possible input, processing, and output combos. See [options](#options) for detailed explanation of the processing input.
8
+
9
+ | input | processing | output |
10
+ | ----- | ---------- | ------ |
11
+ | javascript | minify or optional pass-thru | javascript |
12
+ | css, html | minify css, prepend style tag to html, minify html | css, html |
13
+ | css, html, cssHref | minfy css, prepend style tag to html, prepend link tag to html, minify html | css, html |
14
+ | cssHref, html | prepend link tag to html, minify html | html |
15
+ | javascript, css | minify css, merge style tag into javascript, minify javascript | css, javascript |
16
+ | javascript, css, html | minify css, prepend style tag to html, minify html, merge into javascript, minify javascript | css, html, javascript |
17
+ | javascript, css, html, cssHref | minify css, prepend style tag to html, prepend link tag to html, minify html, merge into javascript, minify javascript | css, html, javascript |
18
+ | javascript, html | minify html, merge into javascript, minify javascript | html, javascript |
19
+ | javascript, html, cssHref | prepend link tag to html, minify html, merge into javascript, minify javascript | html, javascript |
20
+ | javascript, cssHref | add link tag to javascript, minify javascript | javascript |
21
+
22
+ > By default, html minification minifies any css found therein.
23
+
24
+ ## Usage
25
+
26
+ ```javascript
27
+ // Sample usage with most optional options specified
28
+ import {build} from '@localnerve/web-component-build';
29
+ const outputDir = 'some/path/output';
30
+
31
+ const result = await build(outputDir, {
32
+ cssPath: '/some/path/file.css',
33
+ cssLinkHref: '//some/path/file.css',
34
+ jsPath: '/some/path/file.js',
35
+ htmlPath: '/some/path/file.html',
36
+ jsReplacement: '__REPLACEMENT_IN_JS__',
37
+ terserOptions: { /* terser options */ },
38
+ htmlminOptions: { /* html-minifier options */ },
39
+ cleancssOptions: { /* clean-css options */ }
40
+ });
41
+ // html, js, and css file output written to `outputDir`
42
+
43
+ // retrieve processed content
44
+ const [js, css, html] = await Promise.all([
45
+ result.getJs(), result.getCss(), result.getHtml()
46
+ ]);
47
+
48
+ // retrieve output paths
49
+ const [jsPath, cssPath, htmlPath] = [result.jsPath, result.cssPath, result.htmlPath];
50
+ ```
51
+
52
+ ## API
53
+ This library exports a single function that takes an output directory and processing options.
54
+ ```
55
+ build (outputDir, options)
56
+ ```
57
+ `outputDir` {String} - Full path to the output directory where css, html, and javascript output are written.
58
+
59
+ ### Options
60
+ One or more of `cssPath`, `jsPath`, and/or `htmlPath` **must** be supplied. They have no default, so if no options are supplied, this library throws an exception.
61
+
62
+ * `cssPath` {String} - Full path to the input css file
63
+ If supplied:
64
+ + css will be minified using `cleancssOptions`
65
+ + css will be wrapped in a `style` tag
66
+ + css will be inserted into the javascript file if `jsReplacement` and `jsPath` are supplied and no `htmlPath` supplied
67
+ + css will be prepended to the html file if `htmlPath` is supplied
68
+ * `cssLinkHref` {String} - link href to a stylesheet resource to be referenced by the web component
69
+ If supplied:
70
+ + href will be wrapped in a `link` tag
71
+ + resulting `link` will be prepended to the html file if `htmlPath` supplied
72
+ + resulting `link` will be inserted into the javascript file if no `htmlPath` supplied and `jsReplacement` and `jsPath` supplied
73
+ * `htmlPath` {String} - Full path to the input html file
74
+ If supplied:
75
+ + css will be prepended in a `style` tag
76
+ + cssLinkHref will be prepended in a `link` tag
77
+ + html will be inserted into the javascript file if `jsReplacement` and `jsPath` is supplied
78
+ * `jsPath` {String} - Full path to the input javascript file
79
+ * `jsReplacement` {String} - The token to replace with the css or html in the javascript file
80
+ If supplied:
81
+ + A replacement will be attempted in the javascript file
82
+ + If **not supplied** or falsy, No replacement will be attempted and all assets are just copied to `outputDir`
83
+ * `terserOptions` {Object} - The [javascript minifier options](https://github.com/terser/terser/blob/master/README.md#minify-options) object
84
+ Defaults:
85
+ ```
86
+ {
87
+ ecma: 2022
88
+ }
89
+ ```
90
+ * `htmlminOptions` {Object} - The [html minifier options](https://github.com/kangax/html-minifier/blob/gh-pages/README.md#options-quick-reference) object
91
+ Defaults:
92
+ ```
93
+ {
94
+ minifyJS: true,
95
+ minifyCSS: true,
96
+ collapseWhitespace: true,
97
+ removeAttributeQuotes: true,
98
+ removeComments: true
99
+ }
100
+ ```
101
+ * `cleancssOptions` {Object} - The [css minifier options](https://github.com/clean-css/clean-css/blob/master/README.md#constructor-options) object
102
+ Defaults (same as `clean-css` defaults)
103
+ * `minifySkip` {Boolean} - True to skip all minifications, defaults to false
104
+
105
+ ## License
106
+ * [BSD-3 Clasuse, Alex Grant, LocalNerve](LICENSE.md)
package/index.js ADDED
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Web Component Build
3
+ * Assemble and minify a web component from its parts.
4
+ * Expose parts back to the calling build process.
5
+ *
6
+ * Copyright (c) 2023 Alex Grant (@localnerve), LocalNerve LLC
7
+ * Copyrights licensed under the BSD License. See the accompanying LICENSE file for terms.
8
+ */
9
+ export {build, build as default} from './lib/index.js';
package/lib/build.js ADDED
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Web Component Build
3
+ * Builds the component parts
4
+ *
5
+ * Copyright (c) 2023 Alex Grant (@localnerve), LocalNerve LLC
6
+ * Copyrights licensed under the BSD License. See the accompanying LICENSE file for terms.
7
+ */
8
+ import * as path from 'node:path';
9
+ import * as fs from 'node:fs/promises';
10
+ import CleanCss from 'clean-css';
11
+ import {minify as _minifyHtml} from 'html-minifier';
12
+ import {minify as _minifyJs} from 'terser';
13
+
14
+ class WebComponentBuild {
15
+
16
+ /**
17
+ * Construct WebComponentBuild instance.
18
+ *
19
+ * @param {String} outputDir - The full path to output directory.
20
+ * @param {String} [jsPath] - The full path to the input js file.
21
+ * @param {String} [cssPath] - The full path to the input css file.
22
+ * @param {String} [htmlPath] - The full path to the input html file.
23
+ * @param {Boolean} [minifySkip] - flag to skip minification (debug), default false.
24
+ */
25
+ constructor (outputDir, jsPath, cssPath, htmlPath, minifySkip = false) {
26
+ const minimumInput = jsPath || cssPath || htmlPath;
27
+
28
+ if (!minimumInput) {
29
+ throw new Error(
30
+ 'One of jsPath, cssPath, or htmlPath MUST be supplied to do\
31
+ something meaningful. Did you forget something?'
32
+ );
33
+ }
34
+
35
+ this.inputCssFile = cssPath;
36
+ this.inputHtmlFile = htmlPath;
37
+ this.inputJsFile = jsPath;
38
+ this.outputDir = outputDir;
39
+ this.minifySkip = minifySkip;
40
+
41
+ if (jsPath) {
42
+ this.outputJsFile = path.join(this.outputDir, path.basename(jsPath));
43
+ }
44
+ if (htmlPath) {
45
+ this.outputHtmlFile = path.join(this.outputDir, path.basename(htmlPath));
46
+ }
47
+ if (cssPath) {
48
+ this.outputCssFile = path.join(this.outputDir, path.basename(cssPath));
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Minify the input css file and write it to the outputDir.
54
+ *
55
+ * @param {Object} options - clean-css options
56
+ * @returns {String} minified css.
57
+ */
58
+ async minifyCss (options = {}) {
59
+ const cssText = await fs.readFile(this.inputCssFile, {
60
+ encoding: 'utf8'
61
+ });
62
+
63
+ const cleanCss =
64
+ this.minifySkip ? cssText : new CleanCss(options).minify(cssText).styles;
65
+
66
+ await fs.writeFile(this.outputCssFile, cleanCss, {
67
+ encoding: 'utf8'
68
+ });
69
+
70
+ return cleanCss;
71
+ }
72
+
73
+ /**
74
+ * Minify the given html and write it to the outputDir.
75
+ *
76
+ * @param {String} htmlText - The full new html text.
77
+ * @param {Object} options - The html-minifier options.
78
+ * @returns {String} minified html.
79
+ */
80
+ async minifyHtml (htmlText, options = {
81
+ minifyJS: true,
82
+ minifyCSS: true,
83
+ collapseWhitespace: true,
84
+ removeAttributeQuotes: true,
85
+ removeComments: true
86
+ }) {
87
+ const minifiedHtml =
88
+ this.minifySkip ? htmlText : _minifyHtml(htmlText, options);
89
+
90
+ await fs.writeFile(this.outputHtmlFile, minifiedHtml, {
91
+ encoding: 'utf8'
92
+ });
93
+
94
+ return minifiedHtml;
95
+ }
96
+
97
+ /**
98
+ * Minify the given js and write it to the outputDir.
99
+ *
100
+ * @param {String} jsText - The full new js text to process.
101
+ * @param {Object} options - terser minification options.
102
+ * @returns {String} minified js.
103
+ */
104
+ async minifyJs (jsText, options = {
105
+ ecma: 2022
106
+ }) {
107
+ const minifiedJs =
108
+ this.minifySkip ? jsText : (await _minifyJs(jsText, options)).code;
109
+
110
+ await fs.writeFile(this.outputJsFile, minifiedJs, {
111
+ encoding: 'utf8'
112
+ });
113
+
114
+ return minifiedJs;
115
+ }
116
+
117
+ /**
118
+ * Get the output interface.
119
+ * @returns {Object} An interface to access build output content.
120
+ */
121
+ get output () {
122
+ const _cssPath = this.outputCssFile;
123
+ const _htmlPath = this.outputHtmlFile;
124
+ const _jsPath = this.outputJsFile;
125
+
126
+ return {
127
+ get cssPath () {
128
+ return _cssPath;
129
+ },
130
+ async getCss () {
131
+ let cssText;
132
+ if (_cssPath) {
133
+ cssText = await fs.readFile(_cssPath, { encoding: 'utf8' });
134
+ }
135
+ return cssText;
136
+ },
137
+ get htmlPath () {
138
+ return _htmlPath;
139
+ },
140
+ async getHtml () {
141
+ let htmlText;
142
+ if (_htmlPath) {
143
+ htmlText = await fs.readFile(_htmlPath, { encoding: 'utf8' });
144
+ }
145
+ return htmlText;
146
+ },
147
+ get jsPath () {
148
+ return _jsPath;
149
+ },
150
+ async getJs () {
151
+ let jsText;
152
+ if (_jsPath) {
153
+ jsText = await fs.readFile(_jsPath, { encoding: 'utf8' });
154
+ }
155
+ return jsText;
156
+ }
157
+ };
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Create WebComponentBuild instance.
163
+ *
164
+ * @param {String} cssPath - The full path to the input css file.
165
+ * @param {String} htmlPath - The full path to the input html file.
166
+ * @param {String} jsPath - The full path to the input js file.
167
+ * @param {String} outputDir - The full path to output directory.
168
+ * @param {Boolean} [minifySkip] - True to skip minifications (debug), default false.
169
+ * @returns {WebComponentBuild} an instance of WebComponentBuild
170
+ */
171
+ export function createBuild (
172
+ cssPath, htmlPath, jsPath, outputDir, minifySkip = false
173
+ ) {
174
+ return new WebComponentBuild(cssPath, htmlPath, jsPath, outputDir, minifySkip);
175
+ }
package/lib/index.js ADDED
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Web Component Build
3
+ * Assemble and minify a web component from its parts.
4
+ * Expose parts back to the calling build process.
5
+ *
6
+ * Copyright (c) 2023 Alex Grant (@localnerve), LocalNerve LLC
7
+ * Copyrights licensed under the BSD License. See the accompanying LICENSE file for terms.
8
+ */
9
+ import * as fs from 'node:fs/promises';
10
+ import * as cheerio from 'cheerio';
11
+ import {createBuild} from './build.js';
12
+
13
+ /**
14
+ * Build entry point.
15
+ * Possible build outcomes:
16
+ * 1. jsFile only - all contained. Don't expose any css/html to build. Minify js only.
17
+ * 2. css in js - replace the inline css in js. expose the css to build.
18
+ * 3. css in html - add the inline css to html, replace the html in the js, expose css/html to build.
19
+ * 4. csslink in html - add the css link to html, replace the html in the js, expose css/html to build.
20
+ * 5. csslink in js - add the css link to js, replace the html in js.
21
+ * 6. html in js - replace the html in the js, expose html to build.
22
+ *
23
+ * @param {String} outputDir - full path to the output directory
24
+ * @param {Object} [options] - optional options
25
+ * @param {String} [options.jsPath] - full path to the javascript file
26
+ * @param {String} [options.cssPath] - full path to the input css file
27
+ * @param {String} [options.cssLinkHref] - http href to css resource
28
+ * @param {String} [options.htmlPath] - full path to the input html file
29
+ * @param {String} [options.jsReplacement] - Replacement token in the js file
30
+ * @param {Object} [options.terserOptions] - Js minifier options (terser)
31
+ * @param {Object} [options.htmlminOptions] - html-minifier options
32
+ * @param {Object} [options.cleancssOptions] - clean-css options
33
+ * @param {Boolean} [options.minifySkip] - default false, flag to skip all minification (debug)
34
+ * @returns {Object} Interface to getCss, getHtml, getJs for further processing
35
+ */
36
+ export async function build (outputDir, {
37
+ jsPath,
38
+ cssPath,
39
+ cssLinkHref,
40
+ htmlPath,
41
+ jsReplacement,
42
+ terserOptions,
43
+ htmlminOptions,
44
+ cleancssOptions,
45
+ minifySkip = false
46
+ } = {}) {
47
+ const build = createBuild(outputDir, jsPath, cssPath, htmlPath, minifySkip);
48
+ let jsText, cssText, htmlText;
49
+
50
+ if (jsPath) {
51
+ jsText = await fs.readFile(jsPath, { encoding: 'utf8' });
52
+ }
53
+
54
+ if (cssPath) {
55
+ cssText = await build.minifyCss(cleancssOptions);
56
+ }
57
+
58
+ if (htmlPath) {
59
+ htmlText = await fs.readFile(htmlPath, { encoding: 'utf8' });
60
+ const $ = cheerio.load(htmlText);
61
+ if (cssText) {
62
+ $('body').prepend(`<style>${cssText}</style>`);
63
+ }
64
+ if (cssLinkHref) {
65
+ $('body').prepend(`<link href="${cssLinkHref}" rel="stylesheet" />`);
66
+ }
67
+ htmlText = await build.minifyHtml($('body').html(), htmlminOptions);
68
+ }
69
+
70
+ if (jsReplacement) {
71
+ if (htmlText) {
72
+ htmlText = htmlText.replace(/$/mg, '\\');
73
+ if (htmlText.endsWith('\\')) {
74
+ htmlText = htmlText.slice(0, -1);
75
+ }
76
+ jsText = jsText.replace(jsReplacement, htmlText);
77
+ } else if (cssText) {
78
+ jsText = jsText.replace(jsReplacement, `<style>${cssText}</style>`);
79
+ } else if (cssLinkHref) {
80
+ jsText = jsText.replace(
81
+ jsReplacement, `<link href="${cssLinkHref}" rel="stylesheet" />`
82
+ );
83
+ }
84
+ }
85
+
86
+ if (jsText) {
87
+ await build.minifyJs(jsText, terserOptions);
88
+ }
89
+
90
+ return build.output;
91
+ }
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@localnerve/web-component-build",
3
+ "version": "0.2.2",
4
+ "description": "A library to help build web components",
5
+ "main": "index.js",
6
+ "type": "module",
7
+ "exports": {
8
+ "import": "./index.js",
9
+ "default": "./index.js"
10
+ },
11
+ "scripts": {
12
+ "lint": "eslint .",
13
+ "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
14
+ "test:debug": "node --experimental-vm-modules --inspect-brk node_modules/jest/bin/jest.js"
15
+ },
16
+ "files": [
17
+ "lib"
18
+ ],
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/localnerve/web-component-build.git"
22
+ },
23
+ "keywords": [
24
+ "minifier",
25
+ "web components",
26
+ "web",
27
+ "component",
28
+ "build",
29
+ "build tool"
30
+ ],
31
+ "author": {
32
+ "name": "Alex Grant",
33
+ "email": "alex@localnerve.com",
34
+ "url": "https://www.localnerve.com"
35
+ },
36
+ "maintainers": [
37
+ {
38
+ "name": "Alex Grant",
39
+ "email": "alex@localnerve.com",
40
+ "url": "https://www.localnerve.com"
41
+ }
42
+ ],
43
+ "license": "BSD-3-Clause",
44
+ "bugs": {
45
+ "url": "https://github.com/localnerve/web-component-build/issues"
46
+ },
47
+ "homepage": "https://github.com/localnerve/web-component-build#readme",
48
+ "dependencies": {
49
+ "cheerio": "^1.0.0-rc.12",
50
+ "clean-css": "^5.3.3",
51
+ "html-minifier": "^4.0.0",
52
+ "terser": "^5.26.0"
53
+ },
54
+ "devDependencies": {
55
+ "@babel/preset-env": "^7.23.5",
56
+ "eslint": "^8.55.0",
57
+ "jest": "^29.7.0",
58
+ "tempy": "^3.1.0"
59
+ },
60
+ "engines": {
61
+ "node": ">=18"
62
+ }
63
+ }