rollup-plugin-lib-style 1.0.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.
package/README.md ADDED
@@ -0,0 +1,108 @@
1
+ # rollup-plugin-lib-style
2
+
3
+ A Rollup plugin that converts CSS files into CSS modules and also imports the generated CSS files.
4
+ Under the assumption that your library will be bundled by webpack or another bundler, it gives us the ability to consume only the used styles
5
+
6
+ ## Why
7
+
8
+ Today there are 2 main ways to bundle and import style from a library
9
+
10
+ - bundle all the styles into one big CSS file
11
+ - use CSS-in-JS
12
+
13
+ These two ways have some disadvantages, when we are using one big file we are importing style that probably will not be necessary, and when you are using CSS-in-JS you will increase the HTML size
14
+
15
+ This plugin brings you the ability to consume only the used styles from the library
16
+
17
+ ## Install
18
+
19
+ ```bash
20
+ yarn add rollup-plugin-lib-style --dev
21
+ npm i rollup-plugin-lib-style --save-dev
22
+ ```
23
+
24
+ ## Usage
25
+
26
+ ```js
27
+ // rollup.config.js
28
+ import {libStyleLoader} from "rollup-plugin-lib-style"
29
+
30
+ export default {
31
+ plugins: [libStyleLoader()],
32
+ }
33
+ ```
34
+
35
+ After adding this plugin we will be able to use CSS, SCSS, and SASS files (and more languages by adding plugins)
36
+ The imported CSS file will be transformed into a CSS module and a new CSS file will be generated
37
+
38
+ In the js file that imports style file, the import will be changed in the following way:
39
+
40
+ ```js
41
+ import style from "./style.css"
42
+ ```
43
+
44
+ ```js
45
+ import style from "./style.css.js"
46
+ ```
47
+
48
+ The newly generated file will export the CSS module, but also will import the new CSS file.
49
+
50
+ ```js
51
+ // style.css.js"
52
+ import "./myComponent/style.css"
53
+
54
+ var style = {test: "test_cPySKa"}
55
+
56
+ export {style as default}
57
+ ```
58
+
59
+ This gives us the ability to consume only the used style for our component
60
+
61
+ ## Options
62
+
63
+ ### classNamePrefix
64
+
65
+ Type: `string`
66
+ Default: ""
67
+ Description: prefix for the classnames
68
+
69
+ ### scopedName
70
+
71
+ Type: `string`
72
+ Default: "[local]\_[hash:base64:6]"
73
+ Description: customize the scoped name of the classname
74
+
75
+ ### postCssPlugins
76
+
77
+ Type: object[]
78
+ Default: []
79
+ Description: [PostCSS Plugins](https://postcss.org/docs/postcss-plugins)
80
+
81
+ ### loaders
82
+
83
+ Type: Loader[]
84
+ Default: []
85
+ Description: loaders for CSS preprocessor languages
86
+ Example:
87
+
88
+ ```js
89
+ // rollup.config.js
90
+ const lessLoader = {
91
+ name: "lessLoader"
92
+ regex: /\.less$/
93
+ process: ({code, filePath}) => less(code)
94
+ }
95
+
96
+ export default {
97
+ plugins: [libStyleLoader({loaders: [lessLoader]})],
98
+ }
99
+ ```
100
+
101
+ ### exclude
102
+ Type: Array<string | RegExp> | string | RegExp
103
+ Default: null
104
+ Description: exclude files from load by the loader
105
+
106
+ ## License
107
+
108
+ MIT &copy; [Daniel Amenou](https://github.com/DanielAmenou)
@@ -0,0 +1,170 @@
1
+ import { createFilter } from 'rollup-pluginutils';
2
+ import postcss from 'postcss';
3
+ import postcssModules from 'postcss-modules';
4
+ import fs from 'fs-extra';
5
+ import sass from 'sass';
6
+ import path from 'path';
7
+ import glob from 'glob';
8
+
9
+ const defaultScopedName = "[local]_[hash:base64:6]";
10
+
11
+ /**
12
+ * @typedef {object} postCssLoaderOptions
13
+ * @property {object[]} postCssPlugins
14
+ * @property {string} classNamePrefix
15
+ * @property {string} scopedName
16
+ */
17
+
18
+ /**
19
+ * @typedef {object} postCssLoaderProps
20
+ * @property {postCssLoaderOptions} options
21
+ * @property {string} fiePath
22
+ * @property {string} code
23
+ */
24
+
25
+ /**
26
+ * Transform CSS into CSS-modules
27
+ * @param {postCssLoaderProps}
28
+ * @returns
29
+ */
30
+ const postCssLoader = async ({code, fiePath, options}) => {
31
+ const {scopedName = defaultScopedName, postCssPlugins = [], classNamePrefix = ""} = options;
32
+
33
+ const modulesExported = {};
34
+
35
+ postCssPlugins.unshift(
36
+ postcssModules({
37
+ generateScopedName: classNamePrefix + scopedName,
38
+ getJSON: (cssFileName, json) => (modulesExported[cssFileName] = json),
39
+ })
40
+ );
41
+
42
+ const postcssOptions = {
43
+ from: fiePath,
44
+ to: fiePath,
45
+ map: false,
46
+ };
47
+
48
+ const result = await postcss(postCssPlugins).process(code, postcssOptions);
49
+
50
+ // collect dependencies
51
+ const dependencies = [];
52
+ for (const message of result.messages) {
53
+ if (message.type === "dependency") {
54
+ dependencies.add(message.file);
55
+ }
56
+ }
57
+
58
+ // print postcss warnings
59
+ for (const warning of result.warnings()) {
60
+ console.warn(warning.message || warning.text);
61
+ }
62
+
63
+ return {
64
+ code: `export default ${JSON.stringify(modulesExported[fiePath])};`,
65
+ dependencies,
66
+ extracted: {
67
+ id: fiePath,
68
+ code: result.css,
69
+ },
70
+ }
71
+ };
72
+
73
+ const PLUGIN_NAME = "rollup-plugin-lib-style";
74
+ const MAGIC_PATH_REGEX = /@@_MAGIC_PATH_@@/g;
75
+ const MAGIC_PATH = "@@_MAGIC_PATH_@@";
76
+
77
+ const modulesIds = new Set();
78
+
79
+ const outputPaths = [];
80
+
81
+ const defaultLoaders = [
82
+ {
83
+ name: "sass",
84
+ regex: /\.(sass|scss)$/,
85
+ process: ({filePath}) => ({code: sass.compile(filePath).css.toString()}),
86
+ },
87
+ {
88
+ name: "css",
89
+ regex: /\.(css)$/,
90
+ process: ({code}) => ({code}),
91
+ },
92
+ ];
93
+
94
+ const replaceMagicPath = (fileContent) => fileContent.replace(MAGIC_PATH_REGEX, ".");
95
+
96
+ const libStylePlugin = (options) => {
97
+ const {loaders, include, exclude, ...postCssOptions} = options;
98
+ const allLoaders = [...(loaders || []), ...defaultLoaders];
99
+ const filter = createFilter(include, exclude);
100
+ const getLoader = (filepath) => allLoaders.find((loader) => loader.regex.test(filepath));
101
+
102
+ return {
103
+ name: PLUGIN_NAME,
104
+
105
+ options(options) {
106
+ if (!options.output) console.error("missing output options");
107
+ else options.output.forEach((outputOptions) => outputPaths.push(outputOptions.dir));
108
+ },
109
+
110
+ async transform(code, id) {
111
+ const loader = getLoader(id);
112
+ if (!filter(id) || !loader) return null
113
+
114
+ modulesIds.add(id);
115
+
116
+ const rawCss = await loader.process({filePath: id, code});
117
+
118
+ const postCssResult = await postCssLoader({code: rawCss.code, fiePath: id, options: postCssOptions});
119
+
120
+ for (const dependence of postCssResult.dependencies) this.addWatchFile(dependence);
121
+
122
+ const cssFilePath = id.replace(process.cwd(), "").replace(/\\/g, "/");
123
+
124
+ // create a new css file with the generated hash class names
125
+ this.emitFile({
126
+ type: "asset",
127
+ fileName: cssFilePath.replace("/", "").replace(loader.regex, ".css"),
128
+ source: postCssResult.extracted.code,
129
+ });
130
+
131
+ const importStr = `import "${MAGIC_PATH}${cssFilePath.replace(loader.regex, ".css")}";\n`;
132
+
133
+ // create a new js file with css module
134
+ return {
135
+ code: importStr + postCssResult.code,
136
+ map: {mappings: ""},
137
+ }
138
+ },
139
+
140
+ async closeBundle() {
141
+ const importers = [];
142
+ modulesIds.forEach((id) => this.getModuleInfo(id).importers.forEach((importer) => importers.push(path.parse(importer).name + ".js"))); // TODO - add number pattern for duplicate name files
143
+
144
+ const importersPaths = outputPaths
145
+ .reduce((result, currentPath) => {
146
+ result.push(glob.sync(`${path.resolve(process.cwd(), currentPath)}/**/*(${importers.join("|")})`));
147
+ return result
148
+ }, [])
149
+ .flat();
150
+
151
+ //const importersPaths = glob.sync(`lib/**/*(${importers.join("|")})`)
152
+ await Promise.all(
153
+ importersPaths.map((currentPath) =>
154
+ fs
155
+ .readFile(currentPath)
156
+ .then((buffer) => buffer.toString())
157
+ .then(replaceMagicPath)
158
+ .then((fileContent) => fs.writeFile(currentPath, fileContent))
159
+ )
160
+ );
161
+ },
162
+ }
163
+ };
164
+
165
+ const onwarn = (warning, warn) => {
166
+ if (warning.code === "UNRESOLVED_IMPORT" && warning.message.includes(MAGIC_PATH)) return
167
+ warn(warning);
168
+ };
169
+
170
+ export { libStylePlugin, onwarn };
package/lib/index.js ADDED
@@ -0,0 +1,184 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var rollupPluginutils = require('rollup-pluginutils');
6
+ var postcss = require('postcss');
7
+ var postcssModules = require('postcss-modules');
8
+ var fs = require('fs-extra');
9
+ var sass = require('sass');
10
+ var path = require('path');
11
+ var glob = require('glob');
12
+
13
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
14
+
15
+ var postcss__default = /*#__PURE__*/_interopDefaultLegacy(postcss);
16
+ var postcssModules__default = /*#__PURE__*/_interopDefaultLegacy(postcssModules);
17
+ var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
18
+ var sass__default = /*#__PURE__*/_interopDefaultLegacy(sass);
19
+ var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
20
+ var glob__default = /*#__PURE__*/_interopDefaultLegacy(glob);
21
+
22
+ const defaultScopedName = "[local]_[hash:base64:6]";
23
+
24
+ /**
25
+ * @typedef {object} postCssLoaderOptions
26
+ * @property {object[]} postCssPlugins
27
+ * @property {string} classNamePrefix
28
+ * @property {string} scopedName
29
+ */
30
+
31
+ /**
32
+ * @typedef {object} postCssLoaderProps
33
+ * @property {postCssLoaderOptions} options
34
+ * @property {string} fiePath
35
+ * @property {string} code
36
+ */
37
+
38
+ /**
39
+ * Transform CSS into CSS-modules
40
+ * @param {postCssLoaderProps}
41
+ * @returns
42
+ */
43
+ const postCssLoader = async ({code, fiePath, options}) => {
44
+ const {scopedName = defaultScopedName, postCssPlugins = [], classNamePrefix = ""} = options;
45
+
46
+ const modulesExported = {};
47
+
48
+ postCssPlugins.unshift(
49
+ postcssModules__default["default"]({
50
+ generateScopedName: classNamePrefix + scopedName,
51
+ getJSON: (cssFileName, json) => (modulesExported[cssFileName] = json),
52
+ })
53
+ );
54
+
55
+ const postcssOptions = {
56
+ from: fiePath,
57
+ to: fiePath,
58
+ map: false,
59
+ };
60
+
61
+ const result = await postcss__default["default"](postCssPlugins).process(code, postcssOptions);
62
+
63
+ // collect dependencies
64
+ const dependencies = [];
65
+ for (const message of result.messages) {
66
+ if (message.type === "dependency") {
67
+ dependencies.add(message.file);
68
+ }
69
+ }
70
+
71
+ // print postcss warnings
72
+ for (const warning of result.warnings()) {
73
+ console.warn(warning.message || warning.text);
74
+ }
75
+
76
+ return {
77
+ code: `export default ${JSON.stringify(modulesExported[fiePath])};`,
78
+ dependencies,
79
+ extracted: {
80
+ id: fiePath,
81
+ code: result.css,
82
+ },
83
+ }
84
+ };
85
+
86
+ const PLUGIN_NAME = "rollup-plugin-lib-style";
87
+ const MAGIC_PATH_REGEX = /@@_MAGIC_PATH_@@/g;
88
+ const MAGIC_PATH = "@@_MAGIC_PATH_@@";
89
+
90
+ const modulesIds = new Set();
91
+
92
+ const outputPaths = [];
93
+
94
+ const defaultLoaders = [
95
+ {
96
+ name: "sass",
97
+ regex: /\.(sass|scss)$/,
98
+ process: ({filePath}) => ({code: sass__default["default"].compile(filePath).css.toString()}),
99
+ },
100
+ {
101
+ name: "css",
102
+ regex: /\.(css)$/,
103
+ process: ({code}) => ({code}),
104
+ },
105
+ ];
106
+
107
+ const replaceMagicPath = (fileContent) => fileContent.replace(MAGIC_PATH_REGEX, ".");
108
+
109
+ const libStylePlugin = (options) => {
110
+ const {loaders, include, exclude, ...postCssOptions} = options;
111
+ const allLoaders = [...(loaders || []), ...defaultLoaders];
112
+ const filter = rollupPluginutils.createFilter(include, exclude);
113
+ const getLoader = (filepath) => allLoaders.find((loader) => loader.regex.test(filepath));
114
+
115
+ return {
116
+ name: PLUGIN_NAME,
117
+
118
+ options(options) {
119
+ if (!options.output) console.error("missing output options");
120
+ else options.output.forEach((outputOptions) => outputPaths.push(outputOptions.dir));
121
+ },
122
+
123
+ async transform(code, id) {
124
+ const loader = getLoader(id);
125
+ if (!filter(id) || !loader) return null
126
+
127
+ modulesIds.add(id);
128
+
129
+ const rawCss = await loader.process({filePath: id, code});
130
+
131
+ const postCssResult = await postCssLoader({code: rawCss.code, fiePath: id, options: postCssOptions});
132
+
133
+ for (const dependence of postCssResult.dependencies) this.addWatchFile(dependence);
134
+
135
+ const cssFilePath = id.replace(process.cwd(), "").replace(/\\/g, "/");
136
+
137
+ // create a new css file with the generated hash class names
138
+ this.emitFile({
139
+ type: "asset",
140
+ fileName: cssFilePath.replace("/", "").replace(loader.regex, ".css"),
141
+ source: postCssResult.extracted.code,
142
+ });
143
+
144
+ const importStr = `import "${MAGIC_PATH}${cssFilePath.replace(loader.regex, ".css")}";\n`;
145
+
146
+ // create a new js file with css module
147
+ return {
148
+ code: importStr + postCssResult.code,
149
+ map: {mappings: ""},
150
+ }
151
+ },
152
+
153
+ async closeBundle() {
154
+ const importers = [];
155
+ modulesIds.forEach((id) => this.getModuleInfo(id).importers.forEach((importer) => importers.push(path__default["default"].parse(importer).name + ".js"))); // TODO - add number pattern for duplicate name files
156
+
157
+ const importersPaths = outputPaths
158
+ .reduce((result, currentPath) => {
159
+ result.push(glob__default["default"].sync(`${path__default["default"].resolve(process.cwd(), currentPath)}/**/*(${importers.join("|")})`));
160
+ return result
161
+ }, [])
162
+ .flat();
163
+
164
+ //const importersPaths = glob.sync(`lib/**/*(${importers.join("|")})`)
165
+ await Promise.all(
166
+ importersPaths.map((currentPath) =>
167
+ fs__default["default"]
168
+ .readFile(currentPath)
169
+ .then((buffer) => buffer.toString())
170
+ .then(replaceMagicPath)
171
+ .then((fileContent) => fs__default["default"].writeFile(currentPath, fileContent))
172
+ )
173
+ );
174
+ },
175
+ }
176
+ };
177
+
178
+ const onwarn = (warning, warn) => {
179
+ if (warning.code === "UNRESOLVED_IMPORT" && warning.message.includes(MAGIC_PATH)) return
180
+ warn(warning);
181
+ };
182
+
183
+ exports.libStylePlugin = libStylePlugin;
184
+ exports.onwarn = onwarn;
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "rollup-plugin-lib-style",
3
+ "version": "1.0.0",
4
+ "description": "Rollup plugin that converts CSS and more preprocessor languages into CSS modules and also imports the generated CSS files",
5
+ "main": "lib/index.js",
6
+ "module": "lib/index.es.js",
7
+ "files": [
8
+ "lib",
9
+ "types/index.d.ts"
10
+ ],
11
+ "scripts": {
12
+ "build": "rollup --input src/index.js --file lib/index.es.js --format es && rollup --input src/index.js --file lib/index.js --format cjs",
13
+ "prepublishOnly": "npm run build && npm run test",
14
+ "postpublish": "git push && git push --tags",
15
+ "publish:beta": "npm version prerelease --preid=beta -m \"beta version - %s\" && npm publish --tag beta",
16
+ "publish:patch": "npm version patch -m \"patch version - %s\" && npm publish",
17
+ "publish:minor": "npm version minor -m \"minor version - %s\" && npm publish",
18
+ "publish:major": "npm version major -m \"major version - %s\" && npm publish",
19
+ "lint:fix": "eslint **/*.{js,jsx} --fix",
20
+ "lint": "eslint **/*.{js,jsx}",
21
+ "test": "jest",
22
+ "test:cov": "jest --coverage"
23
+ },
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/danielamenou/rollup-plugin-lib-style"
27
+ },
28
+ "keywords": [
29
+ "library",
30
+ "rollup",
31
+ "plugin",
32
+ "style",
33
+ "sass",
34
+ "scss",
35
+ "css"
36
+ ],
37
+ "author": "Daniel Amenou <amenou.daniel@gmail.com>",
38
+ "license": "MIT",
39
+ "engines": {
40
+ "node": ">=16"
41
+ },
42
+ "types": "./types/index.d.ts",
43
+ "devDependencies": {
44
+ "@babel/core": "^7.19.3",
45
+ "@babel/eslint-parser": "^7.19.1",
46
+ "@babel/preset-env": "^7.19.3",
47
+ "@types/jest": "^29.1.2",
48
+ "babel-core": "^6.26.3",
49
+ "babel-jest": "^29.1.2",
50
+ "eslint": "^8.25.0",
51
+ "eslint-config-prettier": "^8.5.0",
52
+ "eslint-config-rem": "^4.0.0",
53
+ "eslint-plugin-import": "^2.26.0",
54
+ "eslint-plugin-prettier": "^4.2.1",
55
+ "eslint-plugin-promise": "^6.0.1",
56
+ "fs-extra": "^10.1.0",
57
+ "jest": "^29.1.2",
58
+ "jest-environment-node-single-context": "^29.0.0",
59
+ "prettier": "^2.7.1",
60
+ "rollup": "^2.79.1"
61
+ },
62
+ "dependencies": {
63
+ "postcss": "^8.4.17",
64
+ "postcss-modules": "^4.0.0",
65
+ "rollup-pluginutils": "^2.8.2",
66
+ "sass": "^1.55.0"
67
+ }
68
+ }
@@ -0,0 +1,26 @@
1
+ import {Plugin, TransformHook, RollupWarning} from "rollup"
2
+
3
+ declare interface ProcessArgs {
4
+ code: string
5
+ filePath: string
6
+ }
7
+
8
+ declare interface Loader {
9
+ name: string
10
+ regex: string
11
+ process: (arg: ProcessArgs) => string
12
+ }
13
+
14
+ declare interface Options {
15
+ include?: string | string[]
16
+ exclude?: string | string[]
17
+ loaders?: Loader[]
18
+ postCssPlugins: object[]
19
+ classNamePrefix: string
20
+ scopedName: string
21
+ }
22
+
23
+ type onwarn = (warning: RollupWarning, defaultHandler: (warning: string | RollupWarning) => void) => void
24
+ type libStylePlugin = (options?: Options) => Plugin
25
+
26
+ export {onwarn, libStylePlugin}