shaderkit 0.1.9 → 0.1.11

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/src/index.ts CHANGED
@@ -1,3 +1,3 @@
1
- export * from './constants'
2
- export * from './minifier'
3
- export * from './tokenizer'
1
+ export * from './constants'
2
+ export * from './minifier'
3
+ export * from './tokenizer'
package/src/minifier.ts CHANGED
@@ -1,100 +1,100 @@
1
- import { type Token, tokenize } from './tokenizer'
2
-
3
- export type MangleMatcher = (token: Token, index: number, tokens: Token[]) => boolean
4
-
5
- export interface MinifyOptions {
6
- /** Whether to rename variables. Will call a {@link MangleMatcher} if specified. Default is `false`. */
7
- mangle: boolean | MangleMatcher
8
- /** A map to read and write renamed variables to when mangling. */
9
- mangleMap: Map<string, string>
10
- /** Whether to rename external variables such as uniforms or varyings. Default is `false`. */
11
- mangleExternals: boolean
12
- }
13
-
14
- const isWord = RegExp.prototype.test.bind(/^\w/)
15
- const isSymbol = RegExp.prototype.test.bind(/[^\w\\]/)
16
- const isName = RegExp.prototype.test.bind(/^[_A-Za-z]/)
17
- const isStorage = RegExp.prototype.test.bind(/^(uniform|in|out|attribute|varying|,)$/)
18
-
19
- /**
20
- * Minifies a string of GLSL or WGSL code.
21
- */
22
- export function minify(
23
- code: string,
24
- { mangle = false, mangleMap = new Map(), mangleExternals = false }: Partial<MinifyOptions> = {},
25
- ): string {
26
- // Escape newlines after directives, skip comments
27
- code = code.replace(/(^\s*#[^\\]*?)(\n|\/[\/\*])/gm, '$1\\$2')
28
-
29
- const exclude = new Set<string>(mangleMap.values())
30
- const tokens: Token[] = tokenize(code).filter((token) => token.type !== 'whitespace' && token.type !== 'comment')
31
-
32
- let mangleIndex: number = 0
33
- let blockIndex: number | null = null
34
- let minified: string = ''
35
- for (let i = 0; i < tokens.length; i++) {
36
- const token = tokens[i]
37
-
38
- // Pad alphanumeric tokens
39
- if (isWord(token.value) && isWord(tokens[i - 1]?.value)) minified += ' '
40
-
41
- // Mark enter/leave block-scope
42
- if (token.value === '{' && isName(tokens[i - 1]?.value)) blockIndex = i - 1
43
- else if (token.value === '}') blockIndex = null
44
-
45
- // Pad symbols around #define and three.js #include (white-space sensitive)
46
- if (
47
- isSymbol(token.value) &&
48
- ((tokens[i - 2]?.value === '#' && tokens[i - 1]?.value === 'include') ||
49
- (tokens[i - 3]?.value === '#' && tokens[i - 2]?.value === 'define'))
50
- )
51
- minified += ' '
52
-
53
- // Mangle declarations and their references
54
- if (
55
- token.type === 'identifier' &&
56
- // Filter variable names
57
- token.value !== 'main' &&
58
- (typeof mangle === 'boolean' ? mangle : mangle(token, i, tokens))
59
- ) {
60
- let renamed = mangleMap.get(token.value)
61
- if (
62
- // no-op
63
- !renamed &&
64
- // Skip struct properties
65
- blockIndex == null &&
66
- // Is declaration, reference, namespace, or comma-separated list
67
- (isName(tokens[i - 1]?.value) ||
68
- // uniform Type { ... } name;
69
- (tokens[i - 1]?.value === '}' && tokens[i + 1]?.value === ';') ||
70
- // float foo, bar;
71
- tokens[i - 1]?.value === ',' ||
72
- // fn (arg: type) -> void
73
- tokens[i + 1]?.value === ':') &&
74
- // Skip shader externals when disabled
75
- (mangleExternals || (!isStorage(tokens[i - 1]?.value) && !isStorage(tokens[i - 2]?.value)))
76
- ) {
77
- while (!renamed || exclude.has(renamed)) {
78
- renamed = ''
79
- mangleIndex++
80
-
81
- let j = mangleIndex
82
- while (j > 0) {
83
- renamed = String.fromCharCode(97 + ((j - 1) % 26)) + renamed
84
- j = Math.floor(j / 26)
85
- }
86
- }
87
-
88
- mangleMap.set(token.value, renamed)
89
- }
90
-
91
- minified += renamed ?? token.value
92
- } else {
93
- if (token.value === '#' && tokens[i - 1]?.value !== '\\') minified += '\n#'
94
- else if (token.value === '\\') minified += '\n'
95
- else minified += token.value
96
- }
97
- }
98
-
99
- return minified.trim()
100
- }
1
+ import { type Token, tokenize } from './tokenizer'
2
+
3
+ export type MangleMatcher = (token: Token, index: number, tokens: Token[]) => boolean
4
+
5
+ export interface MinifyOptions {
6
+ /** Whether to rename variables. Will call a {@link MangleMatcher} if specified. Default is `false`. */
7
+ mangle: boolean | MangleMatcher
8
+ /** A map to read and write renamed variables to when mangling. */
9
+ mangleMap: Map<string, string>
10
+ /** Whether to rename external variables such as uniforms or varyings. Default is `false`. */
11
+ mangleExternals: boolean
12
+ }
13
+
14
+ const isWord = RegExp.prototype.test.bind(/^\w/)
15
+ const isSymbol = RegExp.prototype.test.bind(/[^\w\\]/)
16
+ const isName = RegExp.prototype.test.bind(/^[_A-Za-z]/)
17
+ const isStorage = RegExp.prototype.test.bind(/^(uniform|in|out|attribute|varying|,)$/)
18
+
19
+ /**
20
+ * Minifies a string of GLSL or WGSL code.
21
+ */
22
+ export function minify(
23
+ code: string,
24
+ { mangle = false, mangleMap = new Map(), mangleExternals = false }: Partial<MinifyOptions> = {},
25
+ ): string {
26
+ // Escape newlines after directives, skip comments
27
+ code = code.replace(/(^\s*#[^\\]*?)(\n|\/[\/\*])/gm, '$1\\$2')
28
+
29
+ const exclude = new Set<string>(mangleMap.values())
30
+ const tokens: Token[] = tokenize(code).filter((token) => token.type !== 'whitespace' && token.type !== 'comment')
31
+
32
+ let mangleIndex: number = 0
33
+ let blockIndex: number | null = null
34
+ let minified: string = ''
35
+ for (let i = 0; i < tokens.length; i++) {
36
+ const token = tokens[i]
37
+
38
+ // Pad alphanumeric tokens
39
+ if (isWord(token.value) && isWord(tokens[i - 1]?.value)) minified += ' '
40
+
41
+ // Mark enter/leave block-scope
42
+ if (token.value === '{' && isName(tokens[i - 1]?.value)) blockIndex = i - 1
43
+ else if (token.value === '}') blockIndex = null
44
+
45
+ // Pad symbols around #define and three.js #include (white-space sensitive)
46
+ if (
47
+ isSymbol(token.value) &&
48
+ ((tokens[i - 2]?.value === '#' && tokens[i - 1]?.value === 'include') ||
49
+ (tokens[i - 3]?.value === '#' && tokens[i - 2]?.value === 'define'))
50
+ )
51
+ minified += ' '
52
+
53
+ // Mangle declarations and their references
54
+ if (
55
+ token.type === 'identifier' &&
56
+ // Filter variable names
57
+ token.value !== 'main' &&
58
+ (typeof mangle === 'boolean' ? mangle : mangle(token, i, tokens))
59
+ ) {
60
+ let renamed = mangleMap.get(token.value)
61
+ if (
62
+ // no-op
63
+ !renamed &&
64
+ // Skip struct properties
65
+ blockIndex == null &&
66
+ // Is declaration, reference, namespace, or comma-separated list
67
+ (isName(tokens[i - 1]?.value) ||
68
+ // uniform Type { ... } name;
69
+ (tokens[i - 1]?.value === '}' && tokens[i + 1]?.value === ';') ||
70
+ // float foo, bar;
71
+ tokens[i - 1]?.value === ',' ||
72
+ // fn (arg: type) -> void
73
+ tokens[i + 1]?.value === ':') &&
74
+ // Skip shader externals when disabled
75
+ (mangleExternals || (!isStorage(tokens[i - 1]?.value) && !isStorage(tokens[i - 2]?.value)))
76
+ ) {
77
+ while (!renamed || exclude.has(renamed)) {
78
+ renamed = ''
79
+ mangleIndex++
80
+
81
+ let j = mangleIndex
82
+ while (j > 0) {
83
+ renamed = String.fromCharCode(97 + ((j - 1) % 26)) + renamed
84
+ j = Math.floor(j / 26)
85
+ }
86
+ }
87
+
88
+ mangleMap.set(token.value, renamed)
89
+ }
90
+
91
+ minified += renamed ?? token.value
92
+ } else {
93
+ if (token.value === '#' && tokens[i - 1]?.value !== '\\') minified += '\n#'
94
+ else if (token.value === '\\') minified += '\n'
95
+ else minified += token.value
96
+ }
97
+ }
98
+
99
+ return minified.trim()
100
+ }
package/src/tokenizer.ts CHANGED
@@ -1,81 +1,78 @@
1
- import { WGSL_KEYWORDS, WGSL_SYMBOLS, GLSL_KEYWORDS, GLSL_SYMBOLS } from './constants'
2
-
3
- export type TokenType = 'whitespace' | 'comment' | 'symbol' | 'bool' | 'float' | 'int' | 'identifier' | 'keyword'
4
-
5
- export interface Token<T = TokenType, V = string> {
6
- type: T
7
- value: V
8
- }
9
-
10
- // Checks for WGSL-specific `fn foo(`, `var bar =`, and `let baz =`
11
- const isWGSL = RegExp.prototype.test.bind(/\bfn\s+\w+\s*\(|\b(var|let)\s+\w+\s*[:=]/)
12
-
13
- const isFloat = RegExp.prototype.test.bind(/\.|[eEpP][-+]?\d|[fFhH]$/)
14
- const isBool = RegExp.prototype.test.bind(/^(true|false)$/)
15
-
16
- const ZERO = 48
17
- const NINE = 57
18
- const A = 65
19
- const Z = 90
20
- const LF = 10
21
- const CR = 13
22
- const TAB = 9
23
- const SPACE = 32
24
- const PLUS = 43
25
- const MINUS = 45
26
- const DOT = 46
27
- const UNDERSCORE = 95
28
- const SLASH = 47
29
- const STAR = 42
30
- const HASH = 35
31
- const AT = 64
32
-
33
- const isDigit = (c: number) => ZERO <= c && c <= NINE
34
- const isAlpha = (c: number) => ((c &= ~32), A <= c && c <= Z)
35
- const isLine = (c: number) => c === LF || c === CR
36
- const isSpace = (c: number) => isLine(c) || c === TAB || c === SPACE
37
- const isNumber = (c: number) => isAlpha(c) || isDigit(c) || c === PLUS || c === MINUS || c === DOT
38
- const isIdent = (c: number) => isAlpha(c) || isDigit(c) || c === UNDERSCORE
39
- const isMacro = (c: number) => c === HASH || c === AT
40
-
41
- /**
42
- * Tokenizes a string of GLSL or WGSL code.
43
- */
44
- export function tokenize(code: string, index: number = 0): Token[] {
45
- const tokens: Token[] = []
46
-
47
- let prev: number = -1
48
- const [KEYWORDS, SYMBOLS] = isWGSL(code) ? [WGSL_KEYWORDS, WGSL_SYMBOLS] : [GLSL_KEYWORDS, GLSL_SYMBOLS]
49
- while (index < code.length) {
50
- let value = code[index]
51
- const char = code.charCodeAt(index++)
52
-
53
- if (isSpace(char)) {
54
- while (isSpace(code.charCodeAt(index))) value += code[index++]
55
- tokens.push({ type: 'whitespace', value })
56
- } else if (isDigit(char)) {
57
- while (isNumber(code.charCodeAt(index))) value += code[index++]
58
- if (isFloat(value)) tokens.push({ type: 'float', value })
59
- else tokens.push({ type: 'int', value })
60
- } else if (isIdent(char)) {
61
- while (isIdent(code.charCodeAt(index))) value += code[index++]
62
- if (isBool(value)) tokens.push({ type: 'bool', value })
63
- else if (KEYWORDS.includes(isMacro(prev) ? String.fromCharCode(prev) + value : value))
64
- tokens.push({ type: 'keyword', value })
65
- else tokens.push({ type: 'identifier', value })
66
- } else if (char === SLASH && (code.charCodeAt(index) === SLASH || code.charCodeAt(index) === STAR)) {
67
- const terminator = code.charCodeAt(index) === STAR ? '*/' : '\n'
68
- while (!value.endsWith(terminator)) value += code[index++]
69
- tokens.push({ type: 'comment', value })
70
- } else {
71
- for (const symbol of SYMBOLS) {
72
- if (symbol.length > value.length && code.startsWith(symbol, index - 1)) value = symbol
73
- }
74
- index += value.length - 1
75
- tokens.push({ type: 'symbol', value })
76
- }
77
- prev = char
78
- }
79
-
80
- return tokens
81
- }
1
+ import { WGSL_KEYWORDS, WGSL_SYMBOLS, GLSL_KEYWORDS, GLSL_SYMBOLS } from './constants'
2
+
3
+ export type TokenType = 'whitespace' | 'comment' | 'symbol' | 'bool' | 'float' | 'int' | 'identifier' | 'keyword'
4
+
5
+ export interface Token<T = TokenType, V = string> {
6
+ type: T
7
+ value: V
8
+ }
9
+
10
+ // Checks for WGSL-specific `fn foo(`, `var bar =`, and `let baz =`
11
+ const isWGSL = RegExp.prototype.test.bind(/\bfn\s+\w+\s*\(|\b(var|let)\s+\w+\s*[:=]/)
12
+
13
+ const isFloat = RegExp.prototype.test.bind(/^(\d+\.\d*|\d*\.\d+)([eEpP][-+]?\d+)?[fFhH]?$/)
14
+ const isInt = RegExp.prototype.test.bind(/^(0[xX][\w\d]+|\d+)[iIuU]?$/)
15
+ const isBool = RegExp.prototype.test.bind(/^(true|false)$/)
16
+
17
+ const ZERO = 48
18
+ const NINE = 57
19
+ const A = 65
20
+ const Z = 90
21
+ const LF = 10
22
+ const CR = 13
23
+ const TAB = 9
24
+ const SPACE = 32
25
+ const UNDERSCORE = 95
26
+ const SLASH = 47
27
+ const STAR = 42
28
+ const HASH = 35
29
+ const AT = 64
30
+
31
+ const isDigit = (c: number) => ZERO <= c && c <= NINE
32
+ const isAlpha = (c: number) => ((c &= ~32), A <= c && c <= Z)
33
+ const isLine = (c: number) => c === LF || c === CR
34
+ const isSpace = (c: number) => isLine(c) || c === TAB || c === SPACE
35
+ const isIdent = (c: number) => isAlpha(c) || isDigit(c) || c === UNDERSCORE
36
+ const isMacro = (c: number) => c === HASH || c === AT
37
+
38
+ /**
39
+ * Tokenizes a string of GLSL or WGSL code.
40
+ */
41
+ export function tokenize(code: string, index: number = 0): Token[] {
42
+ const tokens: Token[] = []
43
+
44
+ let prev: number = -1
45
+ const [KEYWORDS, SYMBOLS] = isWGSL(code) ? [WGSL_KEYWORDS, WGSL_SYMBOLS] : [GLSL_KEYWORDS, GLSL_SYMBOLS]
46
+ while (index < code.length) {
47
+ let value = code[index]
48
+ const char = code.charCodeAt(index++)
49
+
50
+ if (isSpace(char)) {
51
+ while (isSpace(code.charCodeAt(index))) value += code[index++]
52
+ tokens.push({ type: 'whitespace', value })
53
+ } else if (isDigit(char)) {
54
+ while (isFloat(value + code[index]) || isInt(value + code[index])) value += code[index++]
55
+ if (isFloat(value)) tokens.push({ type: 'float', value })
56
+ else tokens.push({ type: 'int', value })
57
+ } else if (isIdent(char)) {
58
+ while (isIdent(code.charCodeAt(index))) value += code[index++]
59
+ if (isBool(value)) tokens.push({ type: 'bool', value })
60
+ else if (KEYWORDS.includes(isMacro(prev) ? String.fromCharCode(prev) + value : value))
61
+ tokens.push({ type: 'keyword', value })
62
+ else tokens.push({ type: 'identifier', value })
63
+ } else if (char === SLASH && (code.charCodeAt(index) === SLASH || code.charCodeAt(index) === STAR)) {
64
+ const terminator = code.charCodeAt(index) === STAR ? '*/' : '\n'
65
+ while (!value.endsWith(terminator)) value += code[index++]
66
+ tokens.push({ type: 'comment', value })
67
+ } else {
68
+ for (const symbol of SYMBOLS) {
69
+ if (symbol.length > value.length && code.startsWith(symbol, index - 1)) value = symbol
70
+ }
71
+ index += value.length - 1
72
+ tokens.push({ type: 'symbol', value })
73
+ }
74
+ prev = char
75
+ }
76
+
77
+ return tokens
78
+ }