UNPKG

9.77 kBPlain TextView Raw
1import * as path from 'path'
2import * as fs from 'fs-extra'
3
4import * as t from 'babel-types'
5import * as glob from 'glob'
6import traverse from 'babel-traverse'
7import generate from 'babel-generator'
8import wxTransformer from '@tarojs/transformer-wx'
9
10import {
11 cssImports,
12 printLog,
13 resolveScriptPath,
14 resolveStylePath,
15 isNpmPkg,
16 processTypeEnum,
17 REG_STYLE,
18 REG_TYPESCRIPT,
19 REG_SCRIPT,
20 REG_JSON,
21 REG_FONT,
22 REG_IMAGE,
23 REG_MEDIA,
24 CSS_EXT
25} from '@tarojs/helper'
26
27import { IBuildData } from './ui.types'
28
29let processedScriptFiles: Set<string> = new Set()
30
31export const WEAPP_OUTPUT_NAME = 'weapp'
32export const QUICKAPP_OUTPUT_NAME = 'quickappp'
33export const H5_OUTPUT_NAME = 'h5'
34export const RN_OUTPUT_NAME = 'rn'
35export const TEMP_DIR = '.temp'
36export const RN_TEMP_DIR = 'rn_temp'
37
38interface IComponentObj {
39 name?: string,
40 path: string | null,
41 type?: string
42}
43
44interface IParseAstReturn {
45 styleFiles: string[],
46 scriptFiles: string[],
47 jsonFiles: string[],
48 mediaFiles: string[]
49}
50
51function parseAst (
52 ast: t.File,
53 sourceFilePath: string
54): IParseAstReturn {
55 const styleFiles: string[] = []
56 const scriptFiles: string[] = []
57 const jsonFiles: string[] = []
58 const mediaFiles: string[] = []
59
60 traverse(ast, {
61 Program: {
62 exit (astPath) {
63 astPath.traverse({
64 ImportDeclaration (astPath) {
65 const node = astPath.node
66 const source = node.source
67 const value = source.value
68 const valueExtname = path.extname(value)
69 if (value.indexOf('.') === 0) {
70 if (REG_SCRIPT.test(valueExtname) || REG_TYPESCRIPT.test(valueExtname)) {
71 const vpath = path.resolve(sourceFilePath, '..', value)
72 let fPath = value
73 if (fs.existsSync(vpath) && vpath !== sourceFilePath) {
74 fPath = vpath
75 }
76 if (scriptFiles.indexOf(fPath) < 0) {
77 scriptFiles.push(fPath)
78 }
79 } else if (REG_JSON.test(valueExtname)) {
80 const vpath = path.resolve(sourceFilePath, '..', value)
81 if (fs.existsSync(vpath) && jsonFiles.indexOf(vpath) < 0) {
82 jsonFiles.push(vpath)
83 }
84 } else if (REG_FONT.test(valueExtname) || REG_IMAGE.test(valueExtname) || REG_MEDIA.test(valueExtname)) {
85 const vpath = path.resolve(sourceFilePath, '..', value)
86 if (fs.existsSync(vpath) && mediaFiles.indexOf(vpath) < 0) {
87 mediaFiles.push(vpath)
88 }
89 } else if (REG_STYLE.test(valueExtname)) {
90 const vpath = path.resolve(path.dirname(sourceFilePath), value)
91 if (fs.existsSync(vpath) && styleFiles.indexOf(vpath) < 0) {
92 styleFiles.push(vpath)
93 }
94 } else {
95 const vpath = resolveScriptPath(path.resolve(sourceFilePath, '..', value))
96 if (fs.existsSync(vpath) && scriptFiles.indexOf(vpath) < 0) {
97 scriptFiles.push(vpath)
98 }
99 }
100 }
101 }
102 })
103 }
104 }
105 })
106
107 return {
108 styleFiles,
109 scriptFiles,
110 jsonFiles,
111 mediaFiles
112 }
113}
114
115export function parseEntryAst (ast: t.File, relativeFile: string) {
116 const styleFiles: string[] = []
117 const components: IComponentObj[] = []
118 const importExportName: string[] = []
119 let exportDefaultName: string | null = null
120
121 traverse(ast, {
122 ExportNamedDeclaration (astPath) {
123 const node = astPath.node
124 const specifiers = node.specifiers
125 const source = node.source
126 if (source && source.type === 'StringLiteral') {
127 specifiers.forEach(specifier => {
128 const exported = specifier.exported
129 components.push({
130 name: exported.name,
131 path: resolveScriptPath(path.resolve(path.dirname(relativeFile), source.value))
132 })
133 })
134 } else {
135 specifiers.forEach(specifier => {
136 const exported = specifier.exported
137 importExportName.push(exported.name)
138 })
139 }
140 },
141
142 ExportDefaultDeclaration (astPath) {
143 const node = astPath.node
144 const declaration = node.declaration
145 if (t.isIdentifier(declaration)) {
146 exportDefaultName = declaration.name
147 }
148 },
149
150 Program: {
151 exit (astPath) {
152 astPath.traverse({
153 ImportDeclaration (astPath) {
154 const node = astPath.node
155 const specifiers = node.specifiers
156 const source = node.source
157 const value = source.value
158 const valueExtname = path.extname(value)
159 if (REG_STYLE.test(valueExtname)) {
160 const stylePath = path.resolve(path.dirname(relativeFile), value)
161 if (styleFiles.indexOf(stylePath) < 0) {
162 styleFiles.push(stylePath)
163 }
164 astPath.remove()
165 } else {
166 if (importExportName.length) {
167 importExportName.forEach(nameItem => {
168 specifiers.forEach(specifier => {
169 const local = specifier.local
170 if (local.name === nameItem) {
171 components.push({
172 name: local.name,
173 path: resolveScriptPath(path.resolve(path.dirname(relativeFile), source.value))
174 })
175 }
176 })
177 })
178 }
179 if (exportDefaultName != null) {
180 specifiers.forEach(specifier => {
181 const local = specifier.local
182 if (local.name === exportDefaultName) {
183 components.push({
184 name: local.name,
185 path: resolveScriptPath(path.resolve(path.dirname(relativeFile), source.value))
186 })
187 }
188 })
189 }
190 }
191 }
192 })
193 }
194 }
195 })
196 const code = generate(ast).code
197 return {
198 code,
199 styleFiles,
200 components
201 }
202}
203
204export function isFileToBeCSSModulesMap (filePath) {
205 let isMap = false
206 CSS_EXT.forEach(item => {
207 const reg = new RegExp(`${item}.map.js$`, 'g')
208 if (reg.test(filePath)) {
209 isMap = true
210 }
211 })
212 return isMap
213}
214
215export function copyFileToDist (filePath: string, sourceDir: string, outputDir: string, buildData: IBuildData) {
216 if ((!filePath && !path.isAbsolute(filePath)) || isFileToBeCSSModulesMap(filePath)) {
217 return
218 }
219
220 const { appPath } = buildData
221 const dirname = path.dirname(filePath)
222 const distDirname = dirname.replace(sourceDir, outputDir)
223 const relativePath = path.relative(appPath, filePath)
224 printLog(processTypeEnum.COPY, '发现文件', relativePath)
225 fs.ensureDirSync(distDirname)
226 fs.copyFileSync(filePath, path.format({
227 dir: distDirname,
228 base: path.basename(filePath)
229 }))
230}
231
232function _analyzeFiles (files: string[], sourceDir: string, outputDir: string, buildData: IBuildData) {
233 files.forEach(file => {
234 if (fs.existsSync(file)) {
235 if (processedScriptFiles.has(file)) {
236 return
237 }
238 processedScriptFiles.add(file)
239 const code = fs.readFileSync(file).toString()
240 const transformResult = wxTransformer({
241 code,
242 sourcePath: file,
243 outputPath: file,
244 isNormal: true,
245 isTyped: REG_TYPESCRIPT.test(file)
246 })
247 const {
248 styleFiles,
249 scriptFiles,
250 jsonFiles,
251 mediaFiles
252 } = parseAst(transformResult.ast, file)
253
254 const resFiles = styleFiles.concat(scriptFiles, jsonFiles, mediaFiles)
255
256 if (resFiles.length) {
257 resFiles.forEach(item => {
258 copyFileToDist(item, sourceDir, outputDir, buildData)
259 })
260 }
261 if (scriptFiles.length) {
262 _analyzeFiles(scriptFiles, sourceDir, outputDir, buildData)
263 }
264 if (styleFiles.length) {
265 analyzeStyleFilesImport(styleFiles, sourceDir, outputDir, buildData)
266 }
267 }
268 })
269}
270
271export function analyzeFiles (files: string[], sourceDir: string, outputDir: string, buildData: IBuildData) {
272 _analyzeFiles(files, sourceDir, outputDir, buildData)
273 processedScriptFiles = new Set()
274}
275
276export function analyzeStyleFilesImport (styleFiles, sourceDir, outputDir, buildData: IBuildData) {
277 styleFiles.forEach(item => {
278 if (!fs.existsSync(item)) {
279 return
280 }
281 let content = fs.readFileSync(item).toString()
282 content = content.replace(/(?:@import\s+)?\burl\s*\(\s*("(?:[^\\"\r\n\f]|\\[\s\S])*"|'(?:[^\\'\n\r\f]|\\[\s\S])*'|[^)}\s]+)\s*\)(\s*;?)/g,
283 (m, $1) => {
284 if ($1) {
285 let filePath = $1.replace(/'?"?/g, '')
286 if (filePath.indexOf('.') === 0) {
287 filePath = path.resolve(path.dirname(item), filePath)
288 copyFileToDist(filePath, sourceDir, outputDir, buildData)
289 }
290 }
291 return m
292 })
293 let imports = cssImports(content)
294 if (imports.length > 0) {
295 imports = imports.map(importItem => {
296 if (isNpmPkg(importItem)) {
297 return ''
298 }
299 const filePath = resolveStylePath(path.resolve(path.dirname(item), importItem))
300 copyFileToDist(filePath, sourceDir, outputDir, buildData)
301 return filePath
302 }).filter(item => item)
303 analyzeStyleFilesImport(imports, sourceDir, outputDir, buildData)
304 }
305 })
306}
307
308export function copyAllInterfaceFiles (sourceDir, outputDir, buildData) {
309 const interfaceFiles = glob.sync(path.join(sourceDir, '**/*.d.ts'))
310 if (interfaceFiles && interfaceFiles.length) {
311 interfaceFiles.forEach(item => {
312 copyFileToDist(item, sourceDir, outputDir, buildData)
313 })
314 }
315}