1
+ const path = require ( 'path' )
2
+ const { interpolateName } = require ( 'loader-utils' )
3
+
4
+ const pluginOptions = {
5
+ includePaths : [ ] ,
6
+ localIdentName : '[local]-[hash:base64:6]'
7
+ }
8
+
9
+ const regex = {
10
+ module : / \$ s t y l e \. ( : ? [ \w \d - ] * ) / gm,
11
+ style : / < s t y l e ( \s [ ^ ] * ?) ? > ( [ ^ ] * ?) < \/ s t y l e > / gi
12
+ } ;
13
+
14
+ function generateName ( resourcePath , styles , className ) {
15
+ const filePath = resourcePath
16
+ const fileName = path . basename ( filePath )
17
+ const localName = pluginOptions . localIdentName . length
18
+ ? pluginOptions . localIdentName . replace ( / \[ l o c a l \] / gi, ( ) => className )
19
+ : className
20
+
21
+ const content = `${ styles } -${ filePath } -${ fileName } -${ className } `
22
+
23
+ let interpolatedName = interpolateName ( { resourcePath } , localName , { content } )
24
+
25
+ // prevent class error when the generated classname starts from a non word charater
26
+ if ( / ^ (? ! [ a - z A - Z _ ] ) / . test ( interpolatedName ) ) {
27
+ interpolatedName = `_${ interpolatedName } `
28
+ }
29
+
30
+ // prevent svelte "Unused CSS selector" warning when the generated classname ends by `-`
31
+ if ( interpolatedName . slice ( - 1 ) === '-' ) {
32
+ interpolatedName = interpolatedName . slice ( 0 , - 1 )
33
+ }
34
+
35
+ return interpolatedName
36
+ }
37
+
38
+ const markup = async ( { content, filename } ) => {
39
+ const code = content ;
40
+
41
+ if ( pluginOptions . includePaths . length ) {
42
+ for ( const includePath of pluginOptions . includePaths ) {
43
+ if ( filename . indexOf ( path . resolve ( __dirname , includePath ) ) === - 1 ) {
44
+ return { code } ;
45
+ }
46
+ }
47
+ }
48
+
49
+ if ( ! regex . module . test ( content ) ) {
50
+ return { code } ;
51
+ }
52
+
53
+ const styles = content . match ( regex . style ) ;
54
+ let parsedStyles = null ;
55
+
56
+ let parsedSource = content . replace ( regex . module , ( match , className ) => {
57
+ let replacement = '' ;
58
+
59
+ if ( styles . length ) {
60
+ const classRegex = new RegExp ( `\\.(${ className } )\\b(?![-_])` , 'gm' ) ;
61
+ const toBeParsed = parsedStyles ? parsedStyles : styles [ 0 ] ;
62
+
63
+ if ( classRegex . test ( toBeParsed ) ) {
64
+ const interpolatedName = generateName (
65
+ filename ,
66
+ styles [ 0 ] ,
67
+ className
68
+ ) ;
69
+ parsedStyles = toBeParsed . replace (
70
+ classRegex ,
71
+ ( ) => `:global(.${ interpolatedName } )`
72
+ ) ;
73
+ replacement = interpolatedName ;
74
+ }
75
+ }
76
+ return replacement ;
77
+ } ) ;
78
+
79
+ if ( parsedStyles ) {
80
+ parsedSource = parsedSource . replace ( regex . style , parsedStyles ) ;
81
+ }
82
+
83
+ return {
84
+ code : parsedSource
85
+ }
86
+ } ;
87
+
88
+ export default ( options ) => {
89
+ for ( const option in options ) {
90
+ pluginOptions [ option ] = options [ option ] ;
91
+ }
92
+ return {
93
+ markup,
94
+ }
95
+ } ;
0 commit comments