@moritzloewenstein/vite-plugin-sass-glob-import 5.0.0 → 5.0.1

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,9 @@
1
+ import { Plugin } from 'vite';
2
+
3
+ interface PluginOptions {
4
+ ignorePaths?: string[];
5
+ }
6
+
7
+ declare function sassGlobImports(_options?: PluginOptions): Plugin;
8
+
9
+ export { sassGlobImports as default };
package/dist/index.mjs ADDED
@@ -0,0 +1,90 @@
1
+ // src/index.ts
2
+ import fs from "fs";
3
+ import path from "path";
4
+ import c from "ansi-colors";
5
+ import { globSync } from "glob";
6
+ import { minimatch } from "minimatch";
7
+ var IMPORT_REGEX;
8
+ var options;
9
+ function sassGlobImports(_options = {}) {
10
+ IMPORT_REGEX = /^([ \t]*(?:\/\*.*)?)@(import|use)\s+["']([^"']+\*[^"']*(?:\.scss|\.sass)?)["'];?([ \t]*(?:\/[/*].*)?)$/gm;
11
+ options = _options;
12
+ return {
13
+ name: "sass-glob-import",
14
+ enforce: "pre",
15
+ transform(src, id) {
16
+ const fileName = path.basename(id);
17
+ const filePath = path.dirname(id);
18
+ return {
19
+ code: transform(src, fileName, filePath),
20
+ map: null
21
+ // provide source map if available
22
+ };
23
+ }
24
+ };
25
+ }
26
+ function isSassOrScss(filename) {
27
+ return !fs.statSync(filename).isDirectory() && path.extname(filename).match(/\.sass|\.scss/i);
28
+ }
29
+ function transform(src, fileName, filePath) {
30
+ const isSass = path.extname(fileName).match(/\.sass/i);
31
+ const searchBases = [filePath];
32
+ const ignorePaths = options.ignorePaths || [];
33
+ const contentLinesCount = src.split("\n").length;
34
+ for (let i = 0; i < contentLinesCount; i++) {
35
+ const result = [...src.matchAll(IMPORT_REGEX)];
36
+ if (result.length === 0) continue;
37
+ const [importRule, startComment, importType, globPattern, endComment] = result[0];
38
+ let files = [];
39
+ let basePath = "";
40
+ for (let i2 = 0; i2 < searchBases.length; i2++) {
41
+ basePath = searchBases[i2];
42
+ files = globSync(path.join(basePath, globPattern), {
43
+ cwd: "./",
44
+ windowsPathsNoEscape: true
45
+ }).sort((a, b) => a.localeCompare(b, "en"));
46
+ const globPatternWithoutWildcard = globPattern.split("*")[0];
47
+ if (globPatternWithoutWildcard.length) {
48
+ const directoryExists = fs.existsSync(
49
+ path.join(basePath, globPatternWithoutWildcard)
50
+ );
51
+ if (!directoryExists) {
52
+ console.warn(
53
+ c.yellow(
54
+ `Sass Glob Import: Directories don't exist for the glob pattern "${globPattern}"`
55
+ )
56
+ );
57
+ }
58
+ }
59
+ if (files.length > 0) {
60
+ break;
61
+ }
62
+ }
63
+ const isGlobTrailStatic = !globPattern.split("/").at(-1)?.includes("*");
64
+ const imports = [];
65
+ files.forEach((anyFilename, index) => {
66
+ if (!isSassOrScss(anyFilename)) {
67
+ return;
68
+ }
69
+ const scssFilename = path.relative(basePath, anyFilename).replace(/\\/g, "/").replace(/^\//, "");
70
+ if (!ignorePaths.some((ignorePath) => {
71
+ return minimatch(scssFilename, ignorePath);
72
+ })) {
73
+ const file = isGlobTrailStatic ? `"${scssFilename}" as ${path.parse(scssFilename).name}_${index}` : `"${scssFilename}"`;
74
+ imports.push(`@${importType} ${file}${isSass ? "" : ";"}`);
75
+ }
76
+ });
77
+ if (startComment) {
78
+ imports.unshift(startComment);
79
+ }
80
+ if (endComment) {
81
+ imports.push(endComment);
82
+ }
83
+ const replaceString = imports.join("\n");
84
+ src = src.replace(importRule, replaceString);
85
+ }
86
+ return src;
87
+ }
88
+ export {
89
+ sassGlobImports as default
90
+ };
package/package.json CHANGED
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "name": "@moritzloewenstein/vite-plugin-sass-glob-import",
3
- "version": "5.0.0",
3
+ "version": "5.0.1",
4
4
  "description": "Use glob syntax for imports in your main Sass or SCSS file.",
5
5
  "main": "./dist/index.mjs",
6
6
  "types": "./dist/index.d.mts",
7
+ "files": [
8
+ "dist"
9
+ ],
7
10
  "scripts": {
8
11
  "dev": "vitest",
9
12
  "build": "tsup src/index.ts --format esm --dts",
@@ -1,34 +0,0 @@
1
- name: Lint and Test
2
-
3
- on:
4
- push:
5
- branches: [ main ]
6
- pull_request:
7
- branches: [ main ]
8
-
9
- jobs:
10
- build:
11
- runs-on: ubuntu-latest
12
-
13
- steps:
14
- - uses: actions/checkout@v5
15
-
16
- - uses: actions/setup-node@v5
17
- with:
18
- node-version: 22.x
19
- cache: 'npm'
20
-
21
- - uses: actions/cache@v4
22
- with:
23
- path: ~/.npm
24
- key: npm-${{ hashFiles('package-lock.json') }}
25
- restore-keys: npm-
26
-
27
- - name: Install dependencies
28
- run: npm ci
29
-
30
- - name: Run Biome
31
- run: npm run biome:ci
32
-
33
- - name: Run tests
34
- run: npm run test
@@ -1,38 +0,0 @@
1
- name: Publish
2
-
3
- on:
4
- workflow_run:
5
- workflows: ["Lint and Test"]
6
- branches: [main]
7
- types:
8
- - completed
9
-
10
- permissions:
11
- id-token: write
12
- contents: read
13
-
14
- jobs:
15
- publish:
16
- if: ${{ github.event.workflow_run.conclusion == 'success' }}
17
- runs-on: ubuntu-latest
18
-
19
- steps:
20
- - uses: actions/checkout@v5
21
- - uses: actions/setup-node@v5
22
- with:
23
- node-version: '22.x'
24
- registry-url: 'https://registry.npmjs.org'
25
- - uses: actions/cache@v4
26
- with:
27
- path: ~/.npm
28
- key: npm-${{ hashFiles('package-lock.json') }}
29
- restore-keys: npm-
30
- - name: Install deps and build
31
- run: |
32
- set -e
33
- npm ci
34
- npm run build
35
- - name: Publish to npm
36
- run: npm publish --tag latest --access public --provenance
37
- env:
38
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
package/.nvmrc DELETED
@@ -1 +0,0 @@
1
- 20
package/biome.json DELETED
@@ -1,54 +0,0 @@
1
- {
2
- "$schema": "node_modules/@biomejs/biome/configuration_schema.json",
3
- "assist": { "actions": { "source": { "organizeImports": "on" } } },
4
- "files": {
5
- "ignoreUnknown": true,
6
- "includes": ["**", "!dist"]
7
- },
8
- "vcs": {
9
- "enabled": true,
10
- "clientKind": "git",
11
- "defaultBranch": "main",
12
- "useIgnoreFile": true
13
- },
14
- "linter": {
15
- "enabled": true,
16
- "rules": {
17
- "recommended": true,
18
- "complexity": {
19
- "noForEach": "off"
20
- },
21
- "style": {
22
- "noParameterAssign": "error",
23
- "useAsConstAssertion": "error",
24
- "useDefaultParameterLast": "error",
25
- "useEnumInitializers": "error",
26
- "useSelfClosingElements": "error",
27
- "useSingleVarDeclarator": "error",
28
- "noUnusedTemplateLiteral": "error",
29
- "useNumberNamespace": "error",
30
- "noInferrableTypes": "error",
31
- "noUselessElse": "error"
32
- }
33
- }
34
- },
35
- "formatter": {
36
- "enabled": true
37
- },
38
- "css": {
39
- "formatter": {
40
- "enabled": false
41
- },
42
- "linter": {
43
- "enabled": false
44
- }
45
- },
46
- "graphql": {
47
- "formatter": {
48
- "enabled": false
49
- },
50
- "linter": {
51
- "enabled": false
52
- }
53
- }
54
- }
package/src/index.ts DELETED
@@ -1,122 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import c from "ansi-colors";
4
- import { globSync } from "glob";
5
- import { minimatch } from "minimatch";
6
- import type { Plugin } from "vite";
7
- import type { PluginOptions, TransformResult } from "./types";
8
-
9
- let IMPORT_REGEX: RegExp;
10
- let options: PluginOptions;
11
- export default function sassGlobImports(_options: PluginOptions = {}): Plugin {
12
- IMPORT_REGEX =
13
- /^([ \t]*(?:\/\*.*)?)@(import|use)\s+["']([^"']+\*[^"']*(?:\.scss|\.sass)?)["'];?([ \t]*(?:\/[/*].*)?)$/gm;
14
- options = _options;
15
- return {
16
- name: "sass-glob-import",
17
- enforce: "pre",
18
- transform(src: string, id: string): TransformResult {
19
- const fileName = path.basename(id);
20
- const filePath = path.dirname(id);
21
- return {
22
- code: transform(src, fileName, filePath),
23
- map: null, // provide source map if available
24
- };
25
- },
26
- };
27
- }
28
-
29
- function isSassOrScss(filename: string) {
30
- return (
31
- !fs.statSync(filename).isDirectory() &&
32
- path.extname(filename).match(/\.sass|\.scss/i)
33
- );
34
- }
35
-
36
- function transform(src: string, fileName: string, filePath: string): string {
37
- // Determine if this is Sass (vs SCSS) based on file extension
38
- const isSass = path.extname(fileName).match(/\.sass/i);
39
-
40
- // Store base locations
41
- const searchBases = [filePath];
42
- const ignorePaths = options.ignorePaths || [];
43
- const contentLinesCount = src.split("\n").length;
44
-
45
- for (let i = 0; i < contentLinesCount; i++) {
46
- const result = [...src.matchAll(IMPORT_REGEX)];
47
- if (result.length === 0) continue;
48
-
49
- const [importRule, startComment, importType, globPattern, endComment] =
50
- result[0];
51
-
52
- let files: string[] = [];
53
- let basePath = "";
54
- for (let i = 0; i < searchBases.length; i++) {
55
- basePath = searchBases[i];
56
-
57
- files = globSync(path.join(basePath, globPattern), {
58
- cwd: "./",
59
- windowsPathsNoEscape: true,
60
- }).sort((a, b) => a.localeCompare(b, "en"));
61
-
62
- // Do directories exist matching the glob pattern?
63
- const globPatternWithoutWildcard = globPattern.split("*")[0];
64
- if (globPatternWithoutWildcard.length) {
65
- const directoryExists = fs.existsSync(
66
- path.join(basePath, globPatternWithoutWildcard),
67
- );
68
- if (!directoryExists) {
69
- console.warn(
70
- c.yellow(
71
- `Sass Glob Import: Directories don't exist for the glob pattern "${globPattern}"`,
72
- ),
73
- );
74
- }
75
- }
76
-
77
- if (files.length > 0) {
78
- break;
79
- }
80
- }
81
-
82
- const isGlobTrailStatic = !globPattern.split("/").at(-1)?.includes("*");
83
- const imports = [];
84
- files.forEach((anyFilename: string, index: number) => {
85
- if (!isSassOrScss(anyFilename)) {
86
- return;
87
- }
88
-
89
- const scssFilename = path
90
- // Remove parent base path
91
- .relative(basePath, anyFilename)
92
- .replace(/\\/g, "/")
93
- // Remove leading slash
94
- .replace(/^\//, "");
95
- if (
96
- !ignorePaths.some((ignorePath: string) => {
97
- return minimatch(scssFilename, ignorePath);
98
- })
99
- ) {
100
- const file = isGlobTrailStatic
101
- ? `"${scssFilename}" as ${path.parse(scssFilename).name}_${index}`
102
- : `"${scssFilename}"`;
103
- imports.push(`@${importType} ${file}${isSass ? "" : ";"}`);
104
- }
105
- });
106
-
107
- if (startComment) {
108
- imports.unshift(startComment);
109
- }
110
-
111
- if (endComment) {
112
- imports.push(endComment);
113
- }
114
-
115
- const replaceString = imports.join("\n");
116
- // biome-ignore lint: easier for now
117
- src = src.replace(importRule, replaceString);
118
- }
119
-
120
- // Return the transformed source
121
- return src;
122
- }
package/src/types.ts DELETED
@@ -1,8 +0,0 @@
1
- export interface PluginOptions {
2
- ignorePaths?: string[];
3
- }
4
-
5
- export interface TransformResult {
6
- code: string;
7
- map: null;
8
- }
@@ -1,107 +0,0 @@
1
- import { describe, expect, it, vi } from "vitest";
2
- import sassGlobImportPlugin from "../src";
3
-
4
- const source = `
5
- body {}
6
- @import "files/*.scss";
7
- `;
8
-
9
- describe("it correctly converts glob patterns to inline imports", () => {
10
- // biome-ignore lint: TODO
11
- const plugin: any = sassGlobImportPlugin();
12
-
13
- it("for SCSS", () => {
14
- const expected = `
15
- body {}
16
- @import "files/_file-a.scss";
17
- @import "files/_file-b.scss";
18
- `;
19
- const path = `${__dirname}/virtual-file.scss`;
20
- expect(plugin.transform(source, path)?.code).toEqual(expected);
21
- });
22
-
23
- it("for Sass", () => {
24
- const expected = `
25
- body {}
26
- @import "files/_file-a.scss"
27
- @import "files/_file-b.scss"
28
- `;
29
- const path = `${__dirname}/virtual-file.sass`;
30
- expect(plugin.transform(source, path)?.code).toEqual(expected);
31
- });
32
-
33
- it("with @use", () => {
34
- const source = `
35
- body {}
36
- @use "files/*.scss";
37
- `;
38
- const expected = `
39
- body {}
40
- @use "files/_file-a.scss";
41
- @use "files/_file-b.scss";
42
- `;
43
- const path = `${__dirname}/virtual-file.scss`;
44
- expect(plugin.transform(source, path)?.code).toEqual(expected);
45
- });
46
- });
47
-
48
- describe("it warns for invalid glob paths", () => {
49
- // biome-ignore lint: TODO
50
- const plugin: any = sassGlobImportPlugin();
51
-
52
- it("for SCSS", () => {
53
- const source = `
54
- body {}
55
- @use "foo/**/*.scss";
56
- `;
57
- const expected = `
58
- body {}
59
-
60
- `;
61
- const path = `${__dirname}/virtual-file.scss`;
62
- vi.spyOn(console, "warn");
63
- expect(plugin.transform(source, path)?.code).toEqual(expected);
64
- expect(console.warn).toHaveBeenCalledTimes(1);
65
- });
66
- });
67
-
68
- describe("it correctly converts glob patterns with static trail to namespace imports", () => {
69
- // biome-ignore lint: TODO
70
- const plugin: any = sassGlobImportPlugin();
71
-
72
- it.todo("for SCSS", () => {
73
- //TODO does this even work?
74
- const expected = `
75
- body {}
76
- @import "files/a/foo.scss";
77
- @import "files/b/foo.scss";
78
- `;
79
- const path = `${__dirname}/virtual-file.scss`;
80
- expect(plugin.transform(source, path)?.code).toEqual(expected);
81
- });
82
-
83
- it.todo("for Sass", () => {
84
- //TODO does this even work?
85
- const expected = `
86
- body {}
87
- @import "files/a/foo.scss"
88
- @import "files/b/foo.scss"
89
- `;
90
- const path = `${__dirname}/virtual-file.sass`;
91
- expect(plugin.transform(source, path)?.code).toEqual(expected);
92
- });
93
-
94
- it("with @use", () => {
95
- const source = `
96
- body {}
97
- @use "files/*/foo.scss";
98
- `;
99
- const expected = `
100
- body {}
101
- @use "files/a/foo.scss" as foo_0;
102
- @use "files/b/foo.scss" as foo_1;
103
- `;
104
- const path = `${__dirname}/virtual-file.scss`;
105
- expect(plugin.transform(source, path)?.code).toEqual(expected);
106
- });
107
- });
File without changes
File without changes
File without changes
File without changes
package/tsconfig.json DELETED
@@ -1,12 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ESNext",
4
- "module": "ESNext",
5
- "lib": ["ESNext", "DOM"],
6
- "moduleResolution": "Node",
7
- "allowSyntheticDefaultImports": true,
8
- "strict": true,
9
- "sourceMap": true
10
- },
11
- "include": ["./src"]
12
- }