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