1+ const fs = require ( 'fs/promises' )
2+ const parseRequest = require ( '../utils/parse-request' )
3+ const atImport = require ( 'postcss-import' )
4+ const { default : postcss } = require ( 'postcss' )
5+
16class Node {
2- constructor ( type , condition = null ) {
7+ constructor ( type , condition = null ) {
38 this . type = type // 'If', 'ElseIf', 'Else' 或 'Text'
49 this . condition = condition // If 或 Elif 的条件
510 this . children = [ ]
@@ -8,7 +13,7 @@ class Node {
813}
914
1015// 提取 css string 为 token
11- function tokenize ( cssString ) {
16+ function tokenize ( cssString ) {
1217 const regex = / \/ \* \s * @ m p x - ( i f | e l i f | e l s e | e n d i f ) (?: \s * \( ( .* ?) \) ) ? \s * \* \/ / g
1318 const tokens = [ ]
1419 let lastIndex = 0
@@ -37,12 +42,12 @@ function tokenize (cssString) {
3742}
3843
3944// parse:将生成的 token 数组构造成嵌套的 AST
40- function parse ( cssString ) {
45+ function parse ( cssString ) {
4146 const tokens = tokenize ( cssString )
4247 const ast = [ ]
4348 const nodeStack = [ ]
4449 let currentChildren = ast
45- tokens . forEach ( token => {
50+ tokens . forEach ( ( token ) => {
4651 if ( token . type === 'text' ) {
4752 const node = new Node ( 'Text' )
4853 node . value = token . content
@@ -77,10 +82,10 @@ function parse (cssString) {
7782 return ast
7883}
7984
80- function evaluateCondition ( condition , defs ) {
85+ function evaluateCondition ( condition , defs ) {
8186 try {
8287 const keys = Object . keys ( defs )
83- const values = keys . map ( key => defs [ key ] )
88+ const values = keys . map ( ( key ) => defs [ key ] )
8489 /* eslint-disable no-new-func */
8590 const func = new Function ( ...keys , `return (${ condition } );` )
8691 return func ( ...values )
@@ -90,10 +95,10 @@ function evaluateCondition (condition, defs) {
9095 }
9196}
9297
93- function traverseAndEvaluate ( ast , defs ) {
98+ function traverseAndEvaluate ( ast , defs ) {
9499 let output = ''
95100 let batchedIf = false
96- function traverse ( nodes ) {
101+ function traverse ( nodes ) {
97102 for ( const node of nodes ) {
98103 if ( node . type === 'Text' ) {
99104 output += node . value
@@ -118,10 +123,145 @@ function traverseAndEvaluate (ast, defs) {
118123 return output
119124}
120125
121- module . exports = function ( css ) {
126+ /**
127+ *
128+ * @param {string } content
129+ * @param {Record<string, any> } defs
130+ * @returns
131+ */
132+ function stripCondition ( content , defs ) {
133+ const ast = parse ( content )
134+ const result = traverseAndEvaluate ( ast , defs )
135+ return result
136+ }
137+
138+ /**
139+ * @typedef {Object } StripByPostcssOption
140+ * @property {string } lang 样式语法格式
141+ * @property {string } resourcePath 文件路径
142+ * @property {string } css 源文件
143+ * @property {Record<string, any> } defs 条件定义
144+ * @property {import('webpack').LoaderContext<any>['resolve'] } resolve webpack resolve 方法
145+ */
146+
147+ /**
148+ * @typedef {Object } AtImportConfig
149+ * @property {string } from 当前文件路径
150+ * @property {(filename: string) => Promise<string> | string; } load 加载文件内容的函数
151+ * @property {(id: string, base: string) => Promise<string | null> | string | null; } resolve 解析文件路径的函数
152+ */
153+
154+ /**
155+ *
156+ * @param {Function } callback
157+ * @param {string } name
158+ * @returns
159+ */
160+ const shouldInstallWarning = ( callback , name ) => {
161+ return ( ) => {
162+ try {
163+ return callback ( )
164+ } catch ( error ) {
165+ throw new Error (
166+ `[mpx-strip-conditional-loader]: ${ name } is not installed, please install it first.\norginal Error: ${
167+ error ?. message ?? error . toString ( )
168+ } `,
169+ {
170+ cause : error
171+ }
172+ )
173+ }
174+ }
175+ }
176+ /**
177+ *
178+ * @typedef {import('postcss').ProcessOptions } ProcessOptions
179+ * @typedef {import('postcss').Root } Root
180+ *
181+ * @type {Record<string, ProcessOptions['syntax']> }
182+ */
183+ const styleSyntaxProcesserMap = {
184+ stylus : shouldInstallWarning ( ( ) => require ( 'postcss-styl' ) , 'postcss-styl' ) ,
185+ less : shouldInstallWarning ( ( ) => require ( 'postcss-less' ) , 'postcss-less' ) ,
186+ scss : shouldInstallWarning ( ( ) => require ( 'postcss-scss' ) , 'postcss-scss' )
187+ }
188+
189+ /**
190+ * @param {StripByPostcssOption } options
191+ */
192+ async function stripByPostcss ( options ) {
193+ const syntax = styleSyntaxProcesserMap [ options . lang ] ?. ( )
194+ const defs = options . defs ?? { }
195+
196+ const afterConditionStrip = stripCondition ( options . css , defs )
197+
198+ /**
199+ * @type {import('postcss').AcceptedPlugin[] }
200+ */
201+ const plugins = [
202+ atImport ( {
203+ async load ( filename ) {
204+ let content = await fs . readFile ( filename , 'utf-8' )
205+ const processer = postcss ( plugins )
206+
207+ content = stripCondition ( content , defs )
208+
209+ const { css } = await processer . process ( content , {
210+ syntax,
211+ from : filename ,
212+ to : options . resourcePath
213+ } )
214+ return css
215+ } ,
216+ resolve : ( id , base ) => {
217+ return new Promise ( ( resolve , reject ) => {
218+ options . resolve ( base , id , ( err , res ) => {
219+ if ( err ) return reject ( err )
220+ if ( typeof res !== 'string' ) {
221+ return reject (
222+ new Error (
223+ `[mpx-strip-conditional-loader]: Cannot resolve ${ id } from ${ base } `
224+ )
225+ )
226+ }
227+ resolve ( res )
228+ } )
229+ } )
230+ }
231+ } )
232+ ]
233+
234+ const processer = postcss ( plugins )
235+
236+ return processer . process ( afterConditionStrip , {
237+ from : options . resourcePath ,
238+ syntax
239+ } )
240+ }
241+
242+ /**
243+ *
244+ * @this {import('webpack').LoaderContext<any>}
245+ * @param {string } css
246+ */
247+ module . exports = async function ( css ) {
122248 this . cacheable ( )
249+
250+ const callback = this . async ( )
251+
123252 const mpx = this . getMpx ( )
124- const defs = mpx . defs
125- const ast = parse ( css )
126- return traverseAndEvaluate ( ast , defs )
253+ const { resourcePath, queryObj } = parseRequest ( this . resource )
254+
255+ const result = await stripByPostcss ( {
256+ lang : queryObj . lang ,
257+ resourcePath,
258+ css,
259+ defs : mpx . defs ,
260+ resolve : this . resolve . bind ( this )
261+ } )
262+
263+ callback ( null , result . css , result . map )
127264}
265+
266+ module . exports . stripByPostcss = stripByPostcss
267+ module . exports . stripCondition = stripCondition
0 commit comments