@empjs/plugin-lightningcss 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.
@@ -0,0 +1,4 @@
1
+ import empLightningcssPlugin from './plugin.js';
2
+ import postcss from './postcss-config.js';
3
+ export default empLightningcssPlugin;
4
+ export { postcss };
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ import empLightningcssPlugin from './plugin.js';
2
+ import postcss from './postcss-config.js';
3
+ export default empLightningcssPlugin;
4
+ export { postcss };
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/loader.ts
21
+ var loader_exports = {};
22
+ __export(loader_exports, {
23
+ default: () => loader_default
24
+ });
25
+ module.exports = __toCommonJS(loader_exports);
26
+ var import_lightningcss = require("lightningcss");
27
+ var import_node_buffer = require("node:buffer");
28
+ var LOADER_NAME = "lightningcss-loader";
29
+ async function LightningCSSLoader(source, prevMap) {
30
+ const done = this.async();
31
+ const options = this.getOptions();
32
+ const { implementation, targets, ...opts } = options;
33
+ if (implementation && typeof implementation.transform !== "function") {
34
+ done(
35
+ new TypeError(
36
+ `[${LOADER_NAME}]: options.implementation.transform must be an 'lightningcss' transform function. Received ${typeof implementation.transform}`
37
+ )
38
+ );
39
+ return;
40
+ }
41
+ const transform = implementation?.transform ?? import_lightningcss.transform;
42
+ try {
43
+ const { code, map } = transform({
44
+ filename: this.resourcePath,
45
+ code: import_node_buffer.Buffer.from(source),
46
+ sourceMap: this.sourceMap,
47
+ targets,
48
+ inputSourceMap: this.sourceMap && prevMap ? JSON.stringify(prevMap) : void 0,
49
+ ...opts
50
+ });
51
+ const codeAsString = code.toString();
52
+ done(null, codeAsString, map && JSON.parse(map.toString()));
53
+ } catch (error) {
54
+ done(error);
55
+ }
56
+ }
57
+ var loader_default = LightningCSSLoader;
@@ -0,0 +1,9 @@
1
+ import type { LoaderContext } from '@rspack/core';
2
+ import type { CustomAtRules, TransformOptions } from 'lightningcss';
3
+ export type LightningCSSTransformOptions = Omit<TransformOptions<CustomAtRules>, 'filename' | 'code' | 'inputSourceMap'>;
4
+ type Implementation = unknown;
5
+ export type LightningCSSLoaderOptions = LightningCSSTransformOptions & {
6
+ implementation?: Implementation;
7
+ };
8
+ declare function LightningCSSLoader(this: LoaderContext<LightningCSSLoaderOptions>, source: string, prevMap?: Record<string, any>): Promise<void>;
9
+ export default LightningCSSLoader;
package/dist/loader.js ADDED
@@ -0,0 +1,30 @@
1
+ import { transform as _transform } from 'lightningcss';
2
+ import { Buffer } from 'node:buffer';
3
+ const LOADER_NAME = 'lightningcss-loader';
4
+ async function LightningCSSLoader(source, prevMap) {
5
+ const done = this.async();
6
+ const options = this.getOptions();
7
+ // console.log('options', options)
8
+ const { implementation, targets, ...opts } = options;
9
+ if (implementation && typeof implementation.transform !== 'function') {
10
+ done(new TypeError(`[${LOADER_NAME}]: options.implementation.transform must be an 'lightningcss' transform function. Received ${typeof implementation.transform}`));
11
+ return;
12
+ }
13
+ const transform = implementation?.transform ?? _transform;
14
+ try {
15
+ const { code, map } = transform({
16
+ filename: this.resourcePath,
17
+ code: Buffer.from(source),
18
+ sourceMap: this.sourceMap,
19
+ targets,
20
+ inputSourceMap: this.sourceMap && prevMap ? JSON.stringify(prevMap) : undefined,
21
+ ...opts,
22
+ });
23
+ const codeAsString = code.toString();
24
+ done(null, codeAsString, map && JSON.parse(map.toString()));
25
+ }
26
+ catch (error) {
27
+ done(error);
28
+ }
29
+ }
30
+ export default LightningCSSLoader;
@@ -0,0 +1,10 @@
1
+ import type { Compiler } from '@rspack/core';
2
+ type LightningCSSMinifyPluginOptions = any;
3
+ export declare class LightningCSSMinifyPlugin {
4
+ private readonly options;
5
+ private readonly transform;
6
+ constructor(opts?: LightningCSSMinifyPluginOptions);
7
+ apply(compiler: Compiler): void;
8
+ private transformAssets;
9
+ }
10
+ export default LightningCSSMinifyPlugin;
@@ -0,0 +1,62 @@
1
+ import { transform as _transform } from 'lightningcss';
2
+ import { Buffer } from 'node:buffer';
3
+ const PLUGIN_NAME = 'lightningcss-minify-plugin';
4
+ //
5
+ const CSS_REGEX = /\.css(?:\?.*)?$/i;
6
+ export class LightningCSSMinifyPlugin {
7
+ options;
8
+ transform;
9
+ constructor(opts = {}) {
10
+ const { implementation } = opts;
11
+ if (implementation && typeof implementation.transform !== 'function') {
12
+ throw new TypeError(`[${PLUGIN_NAME}]: implementation.transform must be an 'lightningcss' transform function. Received ${typeof implementation.transform}`);
13
+ }
14
+ this.transform = implementation?.transform ?? _transform;
15
+ this.options = opts;
16
+ }
17
+ apply(compiler) {
18
+ compiler.hooks.compilation.tap(PLUGIN_NAME, compilation => {
19
+ compilation.hooks.processAssets.tapPromise({
20
+ name: PLUGIN_NAME,
21
+ stage: compilation?.PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE,
22
+ }, async () => await this.transformAssets(compilation));
23
+ compilation.hooks.statsPrinter.tap(PLUGIN_NAME, statsPrinter => {
24
+ statsPrinter.hooks.print
25
+ .for('asset.info.minimized')
26
+ .tap(PLUGIN_NAME, (minimized, { green, formatFlag }) => minimized && green && formatFlag ? green(formatFlag('minimized')) : '');
27
+ });
28
+ });
29
+ }
30
+ async transformAssets(compilation) {
31
+ const { options: { devtool }, webpack: { sources: { SourceMapSource, RawSource }, }, } = compilation.compiler;
32
+ const sourcemap = this.options.sourceMap === undefined
33
+ ? (devtool && devtool.includes('source-map'))
34
+ : this.options.sourceMap;
35
+ const { targets: userTargets, ...transformOptions } = this.options;
36
+ const assets = compilation.getAssets().filter(asset =>
37
+ // Filter out already minimized
38
+ !asset.info.minimized &&
39
+ // Filter out by file type
40
+ CSS_REGEX.test(asset.name));
41
+ await Promise.all(assets.map(async (asset) => {
42
+ const { source, map } = asset.source.sourceAndMap();
43
+ const sourceAsString = source.toString();
44
+ const code = typeof source === 'string' ? Buffer.from(source) : source;
45
+ const result = this.transform({
46
+ filename: asset.name,
47
+ code,
48
+ minify: true,
49
+ sourceMap: sourcemap,
50
+ ...transformOptions,
51
+ });
52
+ const codeString = result.code.toString();
53
+ compilation.updateAsset(asset.name, sourcemap
54
+ ? new SourceMapSource(codeString, asset.name, JSON.parse(result.map.toString()), sourceAsString, map, true)
55
+ : new RawSource(codeString), {
56
+ ...asset.info,
57
+ minimized: true,
58
+ });
59
+ }));
60
+ }
61
+ }
62
+ export default LightningCSSMinifyPlugin;
@@ -0,0 +1,11 @@
1
+ import type { GlobalStore } from '@empjs/cli';
2
+ import type { TransformAttributeOptions } from 'lightningcss';
3
+ export type PluginLightningCssType = {
4
+ transform?: boolean | TransformAttributeOptions;
5
+ minify?: boolean | unknown;
6
+ };
7
+ declare const empLightningcssPlugin: (o?: PluginLightningCssType) => {
8
+ name: string;
9
+ rsConfig(store: GlobalStore): Promise<void>;
10
+ };
11
+ export default empLightningcssPlugin;
package/dist/plugin.js ADDED
@@ -0,0 +1,40 @@
1
+ import { browserslistToTargets } from 'lightningcss';
2
+ import browserslist from 'browserslist';
3
+ const uselightningcssLoader = async (store, o) => {
4
+ if (o?.transform === false)
5
+ return;
6
+ const { importResolve, chain } = store;
7
+ const ruleMap = ['sass', 'less', 'css'];
8
+ //
9
+ const transform = o?.transform || {};
10
+ const targets = browserslistToTargets(browserslist('>= 0.25%'));
11
+ //
12
+ const loaderOptions = { ...transform, targets };
13
+ for (const ruleName of ruleMap) {
14
+ const rule = chain.module.rule(ruleName);
15
+ const useLightningcss = rule
16
+ .use('lightningcss')
17
+ .loader(importResolve('./loader.cjs', import.meta.url))
18
+ .options(loaderOptions);
19
+ useLightningcss.before('postcss');
20
+ rule.uses.delete('postcss');
21
+ }
22
+ };
23
+ const uselightningcssMinify = async (store, o) => {
24
+ if (o?.minify === false)
25
+ return;
26
+ const { chain } = store;
27
+ const minify = o?.minify || {};
28
+ const { LightningCSSMinifyPlugin } = await import('./minimizer.js');
29
+ chain.optimization.minimizer('minCss').use(LightningCSSMinifyPlugin, [minify]);
30
+ };
31
+ //
32
+ const empLightningcssPlugin = (o) => {
33
+ return {
34
+ name: '@empjs/plugin-lightningcss',
35
+ async rsConfig(store) {
36
+ await Promise.all([uselightningcssLoader(store, o), uselightningcssMinify(store, o)]);
37
+ },
38
+ };
39
+ };
40
+ export default empLightningcssPlugin;
@@ -0,0 +1,86 @@
1
+ import type { Visitor } from 'lightningcss';
2
+ import type { vwOptions } from './px-to-viewport.js';
3
+ type PxToRemType = {
4
+ rootValue?: number;
5
+ };
6
+ /**
7
+ * // 插件编写 https://github.com/parcel-bundler/lightningcss/blob/master/node/test/visitor.test.mjs
8
+ */
9
+ declare class PostcssConfig {
10
+ /**
11
+ * https://github.com/cuth/postcss-pxtorem
12
+ */
13
+ px_to_rem(op?: PxToRemType): Visitor<any>;
14
+ /**
15
+ * https://github.com/evrone/postcss-px-to-viewport
16
+ */
17
+ px_to_viewport(op: vwOptions): Visitor<any>;
18
+ /**
19
+ * https://www.npmjs.com/package/postcss-apply
20
+ */
21
+ apply(defined: Map<any, any>): Visitor<any>;
22
+ /**
23
+ * https://www.npmjs.com/package/postcss-prefix-selector
24
+ */
25
+ selector_prefix(): Visitor<any>;
26
+ /**
27
+ * https://www.npmjs.com/package/postcss-simple-vars
28
+ */
29
+ static_vars(declared: Map<any, any>): Visitor<any>;
30
+ /**
31
+ * https://www.npmjs.com/package/postcss-url
32
+ */
33
+ url(hostUrl: string): Visitor<any>;
34
+ /**
35
+ * https://www.npmjs.com/package/postcss-env-function
36
+ */
37
+ specific_environment_variables(tokens: {
38
+ [k: string]: any;
39
+ }): Visitor<any>;
40
+ /**
41
+ * https://www.npmjs.com/package/postcss-env-function
42
+ */
43
+ env_function(tokens: any): Visitor<any>;
44
+ /**
45
+ * https://www.npmjs.com/package/@csstools/postcss-design-tokens
46
+ */
47
+ design_tokens(tokens: any): Visitor<any>;
48
+ /**
49
+ * https://github.com/csstools/custom-units
50
+ */
51
+ custom_units(): Visitor<any>;
52
+ /**
53
+ * https://www.npmjs.com/package/postcss-property-lookup
54
+ */
55
+ property_lookup(): Visitor<any>;
56
+ /**
57
+ * https://www.npmjs.com/package/postcss-focus-visible
58
+ */
59
+ focus_visible(): Visitor<any>;
60
+ /**
61
+ * https://github.com/postcss/postcss-dark-theme-class
62
+ */
63
+ dark_theme_class(): Visitor<any>;
64
+ /**
65
+ * https://github.com/postcss/postcss-100vh-fix
66
+ */
67
+ fix_100vh(): Visitor<any>;
68
+ /**
69
+ * https://github.com/MohammadYounes/rtlcss
70
+ */
71
+ logical_transforms(): Visitor<any>;
72
+ /**
73
+ * https://github.com/twbs/mq4-hover-shim
74
+ */
75
+ hover_media_query(): Visitor<any>;
76
+ /**
77
+ * https://github.com/yunusga/postcss-momentum-scrolling
78
+ */
79
+ momentum_scrolling(visitOverflow: any): Visitor<any>;
80
+ /**
81
+ * https://github.com/postcss/postcss-size
82
+ */
83
+ size(): any;
84
+ }
85
+ declare const _default: PostcssConfig;
86
+ export default _default;
@@ -0,0 +1,457 @@
1
+ import { createPxToVwVisitor } from './px-to-viewport.js';
2
+ import { composeVisitors } from 'lightningcss';
3
+ /**
4
+ * // 插件编写 https://github.com/parcel-bundler/lightningcss/blob/master/node/test/visitor.test.mjs
5
+ */
6
+ class PostcssConfig {
7
+ /**
8
+ * https://github.com/cuth/postcss-pxtorem
9
+ */
10
+ px_to_rem(op = {}) {
11
+ const rv = op.rootValue ? op.rootValue : 16;
12
+ return {
13
+ Length(length) {
14
+ if (length.unit === 'px') {
15
+ return {
16
+ unit: 'rem',
17
+ value: length.value / rv,
18
+ };
19
+ }
20
+ },
21
+ };
22
+ }
23
+ /**
24
+ * https://github.com/evrone/postcss-px-to-viewport
25
+ */
26
+ px_to_viewport(op) {
27
+ op = op
28
+ ? op
29
+ : {
30
+ designWidth: 320,
31
+ minPixelValue: 1,
32
+ };
33
+ return composeVisitors([createPxToVwVisitor(op)]);
34
+ }
35
+ /**
36
+ * https://www.npmjs.com/package/postcss-apply
37
+ */
38
+ apply(defined) {
39
+ defined = defined ? defined : new Map();
40
+ return {
41
+ Rule: {
42
+ style(rule) {
43
+ for (const selector of rule.value.selectors) {
44
+ if (selector.length === 1 && selector[0].type === 'type' && selector[0].name.startsWith('--')) {
45
+ defined.set(selector[0].name, rule.value.declarations);
46
+ return { type: 'ignored', value: null };
47
+ }
48
+ }
49
+ rule.value.rules = rule.value.rules.filter(child => {
50
+ if (child.type === 'unknown' && child.value.name === 'apply') {
51
+ for (const token of child.value.prelude) {
52
+ if (token.type === 'dashed-ident' && defined.has(token.value)) {
53
+ const r = defined.get(token.value);
54
+ const decls = rule.value.declarations;
55
+ decls.declarations.push(...r.declarations);
56
+ decls.importantDeclarations.push(...r.importantDeclarations);
57
+ }
58
+ }
59
+ return false;
60
+ }
61
+ return true;
62
+ });
63
+ return rule;
64
+ },
65
+ },
66
+ };
67
+ }
68
+ /**
69
+ * https://www.npmjs.com/package/postcss-prefix-selector
70
+ */
71
+ selector_prefix() {
72
+ return {
73
+ Selector(selector) {
74
+ return [{ type: 'class', name: 'prefix' }, { type: 'combinator', value: 'descendant' }, ...selector];
75
+ },
76
+ };
77
+ }
78
+ /**
79
+ * https://www.npmjs.com/package/postcss-simple-vars
80
+ */
81
+ static_vars(declared) {
82
+ declared = declared ? declared : new Map();
83
+ return {
84
+ Rule: {
85
+ unknown(rule) {
86
+ declared.set(rule.name, rule.prelude);
87
+ return [];
88
+ },
89
+ },
90
+ Token: {
91
+ 'at-keyword'(token) {
92
+ if (declared.has(token.value)) {
93
+ return declared.get(token.value);
94
+ }
95
+ },
96
+ },
97
+ };
98
+ }
99
+ /**
100
+ * https://www.npmjs.com/package/postcss-url
101
+ */
102
+ url(hostUrl) {
103
+ return {
104
+ Url(url) {
105
+ url.url = hostUrl + url.url;
106
+ return url;
107
+ },
108
+ };
109
+ }
110
+ /**
111
+ * https://www.npmjs.com/package/postcss-env-function
112
+ */
113
+ specific_environment_variables(tokens) {
114
+ const ev = {};
115
+ for (const key in tokens) {
116
+ ev[key] = () => tokens[key];
117
+ }
118
+ return {
119
+ EnvironmentVariable: ev,
120
+ };
121
+ }
122
+ /**
123
+ * https://www.npmjs.com/package/postcss-env-function
124
+ */
125
+ env_function(tokens) {
126
+ return {
127
+ EnvironmentVariable(env) {
128
+ if (env.name.type === 'custom') {
129
+ return tokens[env.name.ident];
130
+ }
131
+ },
132
+ };
133
+ }
134
+ /**
135
+ * https://www.npmjs.com/package/@csstools/postcss-design-tokens
136
+ */
137
+ design_tokens(tokens) {
138
+ return {
139
+ Function: {
140
+ 'design-token'(fn) {
141
+ if (fn.arguments.length === 1 &&
142
+ fn.arguments[0].type === 'token' &&
143
+ fn.arguments[0].value.type === 'string') {
144
+ return tokens[fn.arguments[0].value.value];
145
+ }
146
+ },
147
+ },
148
+ };
149
+ }
150
+ /**
151
+ * https://github.com/csstools/custom-units
152
+ */
153
+ custom_units() {
154
+ return {
155
+ Token: {
156
+ dimension(token) {
157
+ if (token.unit.startsWith('--')) {
158
+ return {
159
+ type: 'function',
160
+ value: {
161
+ name: 'calc',
162
+ arguments: [
163
+ {
164
+ type: 'token',
165
+ value: {
166
+ type: 'number',
167
+ value: token.value,
168
+ },
169
+ },
170
+ {
171
+ type: 'token',
172
+ value: {
173
+ type: 'delim',
174
+ value: '*',
175
+ },
176
+ },
177
+ {
178
+ type: 'var',
179
+ value: {
180
+ name: {
181
+ ident: token.unit,
182
+ },
183
+ },
184
+ },
185
+ ],
186
+ },
187
+ };
188
+ }
189
+ },
190
+ },
191
+ };
192
+ }
193
+ /**
194
+ * https://www.npmjs.com/package/postcss-property-lookup
195
+ */
196
+ property_lookup() {
197
+ return {
198
+ Rule: {
199
+ style(rule) {
200
+ const valuesByProperty = new Map();
201
+ for (const decl of rule.value.declarations.declarations) {
202
+ let name = decl.property;
203
+ if (decl.property === 'unparsed') {
204
+ name = decl.value.propertyId.property;
205
+ }
206
+ valuesByProperty.set(name, decl);
207
+ }
208
+ rule.value.declarations.declarations = rule.value.declarations.declarations.map((decl) => {
209
+ // Only single value supported. Would need a way to convert parsed values to unparsed tokens otherwise.
210
+ if (decl.property === 'unparsed' && decl.value.value.length === 1) {
211
+ const token = decl.value.value[0];
212
+ if (token.type === 'token' &&
213
+ token.value.type === 'at-keyword' &&
214
+ valuesByProperty.has(token.value.value)) {
215
+ const v = valuesByProperty.get(token.value.value);
216
+ return {
217
+ /** @type any */
218
+ property: decl.value.propertyId.property,
219
+ value: v.value,
220
+ };
221
+ }
222
+ }
223
+ return decl;
224
+ });
225
+ return rule;
226
+ },
227
+ },
228
+ };
229
+ }
230
+ /**
231
+ * https://www.npmjs.com/package/postcss-focus-visible
232
+ */
233
+ focus_visible() {
234
+ return {
235
+ Rule: {
236
+ style(rule) {
237
+ let clone = null;
238
+ for (const selector of rule.value.selectors) {
239
+ for (const [i, component] of selector.entries()) {
240
+ if (component.type === 'pseudo-class' && component.kind === 'focus-visible') {
241
+ if (clone == null) {
242
+ clone = [...rule.value.selectors.map(s => [...s])];
243
+ }
244
+ selector[i] = { type: 'class', name: 'focus-visible' };
245
+ }
246
+ }
247
+ }
248
+ if (clone) {
249
+ return [rule, { type: 'style', value: { ...rule.value, selectors: clone } }];
250
+ }
251
+ },
252
+ },
253
+ };
254
+ }
255
+ /**
256
+ * https://github.com/postcss/postcss-dark-theme-class
257
+ */
258
+ dark_theme_class() {
259
+ return {
260
+ Rule: {
261
+ media(rule) {
262
+ const q = rule.value.query.mediaQueries[0];
263
+ if (q.condition?.type === 'feature' &&
264
+ q.condition.value.type === 'plain' &&
265
+ q.condition.value.name === 'prefers-color-scheme' &&
266
+ q.condition.value.value.value === 'dark') {
267
+ const clonedRules = [rule];
268
+ for (const r of rule.value.rules) {
269
+ if (r.type === 'style') {
270
+ const clonedSelectors = [];
271
+ for (const selector of r.value.selectors) {
272
+ clonedSelectors.push([
273
+ { type: 'type', name: 'html' },
274
+ { type: 'attribute', name: 'theme', operation: { operator: 'equal', value: 'dark' } },
275
+ { type: 'combinator', value: 'descendant' },
276
+ ...selector,
277
+ ]);
278
+ selector.unshift({ type: 'type', name: 'html' }, {
279
+ type: 'pseudo-class',
280
+ kind: 'not',
281
+ selectors: [[{ type: 'attribute', name: 'theme', operation: { operator: 'equal', value: 'light' } }]],
282
+ }, { type: 'combinator', value: 'descendant' });
283
+ }
284
+ clonedRules.push({ type: 'style', value: { ...r.value, selectors: clonedSelectors } });
285
+ }
286
+ }
287
+ return clonedRules;
288
+ }
289
+ },
290
+ },
291
+ };
292
+ }
293
+ /**
294
+ * https://github.com/postcss/postcss-100vh-fix
295
+ */
296
+ fix_100vh() {
297
+ return {
298
+ Rule: {
299
+ style(style) {
300
+ let cloned;
301
+ for (const property of style.value.declarations.declarations) {
302
+ if (property.property === 'height' &&
303
+ property.value.type === 'length-percentage' &&
304
+ property.value.value.type === 'dimension' &&
305
+ property.value.value.value.unit === 'vh' &&
306
+ property.value.value.value.value === 100) {
307
+ if (!cloned) {
308
+ cloned = structuredClone(style);
309
+ cloned.value.declarations.declarations = [];
310
+ }
311
+ cloned.value.declarations.declarations.push({
312
+ ...property,
313
+ value: {
314
+ type: 'stretch',
315
+ vendorPrefix: ['webkit'],
316
+ },
317
+ });
318
+ }
319
+ }
320
+ if (cloned) {
321
+ return [
322
+ style,
323
+ {
324
+ type: 'supports',
325
+ value: {
326
+ condition: {
327
+ type: 'declaration',
328
+ propertyId: {
329
+ property: '-webkit-touch-callout',
330
+ },
331
+ value: 'none',
332
+ },
333
+ loc: style.value.loc,
334
+ rules: [cloned],
335
+ },
336
+ },
337
+ ];
338
+ }
339
+ },
340
+ },
341
+ };
342
+ }
343
+ /**
344
+ * https://github.com/MohammadYounes/rtlcss
345
+ */
346
+ logical_transforms() {
347
+ return {
348
+ Rule: {
349
+ style(style) {
350
+ let cloned;
351
+ for (const property of style.value.declarations.declarations) {
352
+ if (property.property === 'transform') {
353
+ const clonedTransforms = property.value.map(transform => {
354
+ if (transform.type !== 'translateX') {
355
+ return transform;
356
+ }
357
+ if (!cloned) {
358
+ cloned = structuredClone(style);
359
+ cloned.value.declarations.declarations = [];
360
+ }
361
+ let value;
362
+ switch (transform.value.type) {
363
+ case 'dimension':
364
+ value = {
365
+ type: 'dimension',
366
+ value: { unit: transform.value.value.unit, value: -transform.value.value.value },
367
+ };
368
+ break;
369
+ case 'percentage':
370
+ value = { type: 'percentage', value: -transform.value.value };
371
+ break;
372
+ case 'calc':
373
+ value = { type: 'calc', value: { type: 'product', value: [-1, transform.value.value] } };
374
+ break;
375
+ }
376
+ return {
377
+ type: 'translateX',
378
+ value,
379
+ };
380
+ });
381
+ if (cloned) {
382
+ cloned.value.selectors.at(-1).push({ type: 'pseudo-class', kind: 'dir', direction: 'rtl' });
383
+ cloned.value.declarations.declarations.push({
384
+ ...property,
385
+ value: clonedTransforms,
386
+ });
387
+ }
388
+ }
389
+ }
390
+ if (cloned) {
391
+ return [style, cloned];
392
+ }
393
+ },
394
+ },
395
+ };
396
+ }
397
+ /**
398
+ * https://github.com/twbs/mq4-hover-shim
399
+ */
400
+ hover_media_query() {
401
+ return {
402
+ Rule: {
403
+ media(media) {
404
+ const mediaQueries = media.value.query.mediaQueries;
405
+ if (mediaQueries.length === 1 &&
406
+ mediaQueries[0].condition &&
407
+ mediaQueries[0].condition.type === 'feature' &&
408
+ mediaQueries[0].condition.value.type === 'boolean' &&
409
+ mediaQueries[0].condition.value.name === 'hover') {
410
+ for (const rule of media.value.rules) {
411
+ if (rule.type === 'style') {
412
+ for (const selector of rule.value.selectors) {
413
+ selector.unshift({ type: 'class', name: 'hoverable' }, { type: 'combinator', value: 'descendant' });
414
+ }
415
+ }
416
+ }
417
+ return media.value.rules;
418
+ }
419
+ },
420
+ },
421
+ };
422
+ }
423
+ /**
424
+ * https://github.com/yunusga/postcss-momentum-scrolling
425
+ */
426
+ momentum_scrolling(visitOverflow) {
427
+ return {
428
+ Declaration: {
429
+ overflow: visitOverflow,
430
+ 'overflow-x': visitOverflow,
431
+ 'overflow-y': visitOverflow,
432
+ },
433
+ };
434
+ }
435
+ /**
436
+ * https://github.com/postcss/postcss-size
437
+ */
438
+ size() {
439
+ return {
440
+ Declaration: {
441
+ custom: {
442
+ size(property) {
443
+ if (property.value[0].type === 'length') {
444
+ /** @type {import('../ast').Size} */
445
+ const value = { type: 'length-percentage', value: { type: 'dimension', value: property.value[0].value } };
446
+ return [
447
+ { property: 'width', value },
448
+ { property: 'height', value },
449
+ ];
450
+ }
451
+ },
452
+ },
453
+ },
454
+ };
455
+ }
456
+ }
457
+ export default new PostcssConfig();
@@ -0,0 +1,17 @@
1
+ interface Options {
2
+ designWidth: number;
3
+ minPixelValue: number;
4
+ excludeSelectors: {
5
+ type: string;
6
+ name: RegExp | string;
7
+ }[];
8
+ }
9
+ export type vwOptions = Partial<Options>;
10
+ export declare function createPxToVwVisitor(userOptions?: vwOptions): {
11
+ Selector(selectors: import("lightningcss").Selector): void;
12
+ Length(length: import("lightningcss").LengthValue): {
13
+ unit: "vw";
14
+ value: number;
15
+ } | undefined;
16
+ };
17
+ export {};
@@ -0,0 +1,49 @@
1
+ const baseOptions = {
2
+ designWidth: 320,
3
+ minPixelValue: 1,
4
+ excludeSelectors: [],
5
+ };
6
+ function createExcludeFilter(excludes) {
7
+ const isExclude = (testItem) => {
8
+ if (!testItem.name) {
9
+ return false;
10
+ }
11
+ for (const rule of excludes) {
12
+ if (testItem.type === rule.type) {
13
+ if (typeof rule.name === 'string' && rule.name === testItem.name) {
14
+ return true;
15
+ }
16
+ if (typeof rule.name === 'object' && rule.name.test(testItem.name)) {
17
+ return true;
18
+ }
19
+ }
20
+ }
21
+ return false;
22
+ };
23
+ return isExclude;
24
+ }
25
+ export function createPxToVwVisitor(userOptions = {}) {
26
+ const options = Object.assign(baseOptions, userOptions);
27
+ const isExclude = createExcludeFilter(options.excludeSelectors);
28
+ let skipCurrentSelector = false;
29
+ return {
30
+ Selector(selectors) {
31
+ skipCurrentSelector = false;
32
+ for (const selector of selectors) {
33
+ if (isExclude(selector)) {
34
+ skipCurrentSelector = true;
35
+ }
36
+ }
37
+ },
38
+ Length(length) {
39
+ if (length.unit === 'px' && !skipCurrentSelector) {
40
+ if (length.value > options.minPixelValue) {
41
+ return {
42
+ unit: 'vw',
43
+ value: (length.value / options.designWidth) * 100,
44
+ };
45
+ }
46
+ }
47
+ },
48
+ };
49
+ }
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@empjs/plugin-lightningcss",
3
+ "version": "1.0.0",
4
+ "description": "emp v3 lightningcss",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "type": "module",
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "maintainers": [
12
+ "xuhongbin",
13
+ "ckken"
14
+ ],
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/empjs/emp.git",
18
+ "directory": "packages/plugin-lightningcss"
19
+ },
20
+ "publishConfig": {
21
+ "access": "public"
22
+ },
23
+ "scripts": {
24
+ "dev": "rimraf dist && pnpm run /^dev:.*/",
25
+ "dev:tsc": "tsc -w",
26
+ "dev:tsc-alias": "tsc-alias -w",
27
+ "dev:cjs": "cross-env ENV=dev node esbuild.js",
28
+ "build": "rimraf dist && pnpm run /^build:.*/",
29
+ "build:tsc": "tsc && tsc-alias",
30
+ "build:cjs": "cross-env ENV=prod node esbuild.js"
31
+ },
32
+ "engines": {
33
+ "node": ">=20.0.0"
34
+ },
35
+ "keywords": [],
36
+ "author": "",
37
+ "license": "ISC",
38
+ "dependencies": {
39
+ "browserslist": "^4.23.0",
40
+ "lightningcss": "^1.24.1"
41
+ },
42
+ "devDependencies": {
43
+ "@empjs/cli": "workspace:*",
44
+ "@rspack/core": "0.5.8",
45
+ "cross-env": "^7.0.3",
46
+ "esbuild": "^0.19.5",
47
+ "rimraf": "^5.0.5",
48
+ "tsc-alias": "^1.8.8"
49
+ }
50
+ }