omip - sync template-p

This commit is contained in:
dntzhang 2019-02-28 21:05:41 +08:00
parent ec0357b026
commit fcd21e345f
76 changed files with 14006 additions and 5 deletions

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Omi Team
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -5,7 +5,7 @@
"description": "",
"scripts": {
"start": "npm run build:weapp -- --watch",
"build:weapp": "node ../taro-cli/bin/taro build --type weapp",
"build:weapp": "node ./scripts/taro-cli/bin/taro build --type weapp",
"build:swan": "taro build --type swan",
"build:alipay": "taro build --type alipay",
"build:tt": "taro build --type tt",
@ -29,7 +29,71 @@
"@tarojs/taro-tt": "1.2.13",
"@tarojs/taro-weapp": "1.2.13",
"nerv-devtools": "^1.3.9",
"nervjs": "^1.3.9"
"nervjs": "^1.3.9",
"@tarojs/taroize": "1.2.13",
"autoprefixer": "^8.4.1",
"babel-core": "^6.26.3",
"babel-generator": "^6.26.1",
"babel-plugin-danger-remove-unused-import": "^1.1.1",
"babel-plugin-remove-dead-code": "^1.3.2",
"babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-plugin-transform-define": "^1.3.0",
"babel-plugin-transform-jsx-to-stylesheet": "1.2.13",
"babel-plugin-transform-react-jsx": "^6.24.1",
"babel-template": "^6.26.0",
"babel-traverse": "^6.26.0",
"babel-types": "^6.26.0",
"babylon": "^6.18.0",
"better-babel-generator": "^6.26.1",
"chalk": "^2.3.2",
"chokidar": "^2.0.3",
"commander": "^2.15.0",
"cross-spawn": "^6.0.5",
"css-to-react-native-transform": "^1.4.0",
"ejs": "^2.6.1",
"envinfo": "^6.0.1",
"fs-extra": "^5.0.0",
"generic-names": "^2.0.1",
"glob": "^7.1.2",
"inquirer": "^5.2.0",
"klaw": "^2.1.1",
"latest-version": "^4.0.0",
"lodash": "^4.17.5",
"mem-fs": "^1.1.3",
"mem-fs-editor": "^4.0.0",
"minimatch": "^3.0.4",
"ora": "^2.0.0",
"postcss": "^6.0.22",
"postcss-modules-extract-imports": "^1.1.0",
"postcss-modules-local-by-default": "^1.2.0",
"postcss-modules-resolve-imports": "^1.3.0",
"postcss-modules-scope": "^1.1.0",
"postcss-modules-values": "^1.3.0",
"postcss-pxtransform": "1.2.13",
"postcss-taro-unit-transform": "1.2.13",
"postcss-url": "^7.3.2",
"prettier": "^1.14.3",
"prop-types": "^15.6.2",
"resolve": "^1.6.0",
"semver": "^5.5.0",
"shelljs": "^0.8.1",
"through2": "^2.0.3",
"vinyl": "^2.1.0",
"vinyl-fs": "^3.0.2",
"@babel/code-frame": "^7.0.0-beta.44",
"babel-eslint": "^8.2.3",
"babel-helper-evaluate-path": "^0.5.0",
"babel-helper-mark-eval-scopes": "^0.4.3",
"babel-helper-remove-or-void": "^0.4.3",
"babel-plugin-minify-dead-code": "^0.5.2",
"babel-plugin-syntax-dynamic-import": "^6.18.0",
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-plugin-transform-es2015-template-literals": "^6.22.0",
"babel-plugin-transform-flow-strip-types": "^6.22.0",
"eslint": "^4.15.0",
"eslint-plugin-taro": "1.2.13",
"html": "^1.0.0",
"typescript": "^3.2.2"
},
"devDependencies": {
"@types/react": "^16.4.6",

View File

@ -0,0 +1,24 @@
#! /usr/bin/env node
const program = require('commander')
const { getPkgVersion, printPkgVersion } = require('../src/util')
printPkgVersion()
const startTime = new Date('2019-1-1 00:00').getTime()
const endTime = new Date('2019-1-2 00:00').getTime()
const nowTime = Date.now()
if (nowTime >= startTime && nowTime <= endTime) {
const yearTxt = String(require('fs-extra').readFileSync(require('path').resolve(__dirname, 'year.txt')))
console.log(require('chalk').rgb(255, 87, 34)(yearTxt))
}
program
.version(getPkgVersion())
.usage('<command> [options]')
.command('init [projectName]', 'Init a project with default templete')
.command('build', 'Build a project with options')
.command('update', 'Update packages of taro')
.command('convert', 'Convert weapp to taro')
.command('info', 'Diagnostics Taro env info')
.parse(process.argv)

View File

@ -0,0 +1,55 @@
#!/usr/bin/env node
const path = require('path')
const fs = require('fs-extra')
const program = require('commander')
const chalk = require('chalk')
const _ = require('lodash')
const build = require('../src/build')
const { PROJECT_CONFIG } = require('../src/util')
const projectConfPath = path.join(process.cwd(), PROJECT_CONFIG)
program
.option('--type [typeName]', 'Build type, weapp/h5/rn/swan/alipay/tt')
.option('--watch', 'Watch mode')
.option('--env [env]', 'Env type')
.option('--ui', 'Build Taro UI library')
.parse(process.argv)
const args = program.args
const { type, watch, ui } = program
let { env } = program
env = process.env.NODE_ENV || env
if (ui) {
console.log(chalk.green(`开始编译 UI 库`))
build(args, {
type: 'ui',
watch
})
return
}
if (!fs.existsSync(projectConfPath)) {
console.log(chalk.red(`找不到项目配置文件${PROJECT_CONFIG}请确定当前目录是Taro项目根目录!`))
process.exit(1)
}
if (env) {
process.env.NODE_ENV = env
} else {
if (watch) {
process.env.NODE_ENV = 'development'
} else {
process.env.NODE_ENV = 'production'
}
}
const projectConf = require(projectConfPath)(_.merge)
console.log(chalk.green(`开始编译项目 ${chalk.bold(projectConf.projectName)}`))
build(args, {
type,
watch
})

View File

@ -0,0 +1,12 @@
#!/usr/bin/env node
const program = require('commander')
const Convertor = require('../src/convertor')
program
.parse(process.argv)
const convertor = new Convertor()
convertor.run()

View File

@ -0,0 +1,53 @@
#!/usr/bin/env node
const fs = require('fs')
const path = require('path')
const envinfo = require('envinfo')
const {getPkgVersion, UPDATE_PACKAGE_LIST} = require('../src/util')
const process = require('process')
const program = require('commander')
const npmPackages = UPDATE_PACKAGE_LIST.concat(['react', 'react-native', 'nervjs', 'expo'])
program.parse(process.argv)
const args = program.args
if (args.length === 1) {
switch (args[0]) {
case 'rn': {
rnInfo({
SDKs: ['iOS SDK', 'Android SDK']
})
break
}
default:
info()
}
} else {
info()
}
function rnInfo (options) {
const appPath = process.cwd()
const tempPath = path.join(appPath, '.rn_temp')
if (fs.lstatSync(tempPath).isDirectory()) {
process.chdir('.rn_temp')
info(options)
}
}
async function info (options) {
let info = await envinfo.run(
{
System: ['OS', 'Shell'],
Binaries: ['Node', 'Yarn', 'npm'],
npmPackages,
npmGlobalPackages: ['typescript'],
...options
},
{
title: `Taro CLI ${getPkgVersion()} environment info`
}
)
console.log(info)
}

View File

@ -0,0 +1,40 @@
#!/usr/bin/env node
const program = require('commander')
const Project = require('../src/project')
program
.option('--name [name]', '项目名称')
.option('--description [description]', '项目介绍')
.option('--typescript', '使用TypeScript')
.option('--no-typescript', '不使用TypeScript')
.option('--template [template]', '项目模板(default/redux/mobx)')
.option('--css [css]', 'CSS预处理器(sass/less/stylus/none)')
.parse(process.argv)
const args = program.args
const { template, description, name, css } = program
let typescript = ''
/**
* 非标准做法
* 为了兼容不指定typescript参数时在inquirer中询问是否使用typescript的情况
*/
if (program.rawArgs.indexOf('--typescript') !== -1) {
typescript = true
} else if (program.rawArgs.indexOf('--no-typescript') !== -1) {
typescript = false
}
const projectName = args[0] || name
const project = new Project({
projectName,
template,
description,
typescript,
css
})
project.create()

View File

@ -0,0 +1,118 @@
#!/usr/bin/env node
const path = require('path')
const fs = require('fs-extra')
const program = require('commander')
const chalk = require('chalk')
const { getPkgItemByKey } = require('../src/util')
const ora = require('ora')
const exec = require('child_process').exec
const getLatestVersion = require('latest-version')
const { PROJECT_CONFIG, UPDATE_PACKAGE_LIST} = require('../src/util')
const projectConfPath = path.join(process.cwd(), PROJECT_CONFIG)
const pkgPath = path.join(process.cwd(), 'package.json')
const { shouldUseYarn, shouldUseCnpm } = require('../src/util')
const pkgName = getPkgItemByKey('name')
// 这里没有使用 command 的形式taro-update-self
program.parse(process.argv)
const args = program.args
if (args.length === 1) {
switch (args[0]) {
case 'self': {
updateSelf()
break
}
case 'project': {
updateProject()
break
}
default:
info()
}
} else {
info()
}
function info () {
console.log(chalk.red('命令错误:'))
console.log(`${chalk.green('taro update self')} 更新 Taro 开发工具 taro-cli 到最新版本`)
console.log(`${chalk.green('taro update project')} 更新项目所有 Taro 相关依赖到最新版本...`)
}
function updateSelf () {
let command
if (shouldUseCnpm()) {
command = 'cnpm i -g @tarojs/cli@latest'
} else {
command = 'npm i -g @tarojs/cli@latest'
}
let child = exec(command)
const spinner = ora('即将将 Taro 开发工具 taro-cli 更新到最新版本...').start()
child.stdout.on('data', function (data) {
console.log(data)
spinner.stop()
})
child.stderr.on('data', function (data) {
console.log(data)
spinner.stop()
})
}
async function updateProject () {
if (!fs.existsSync(projectConfPath)) {
console.log(chalk.red(`找不到项目配置文件${PROJECT_CONFIG}请确定当前目录是Taro项目根目录!`))
process.exit(1)
}
const packageMap = require(pkgPath)
const version = await getLatestVersion(pkgName)
// 更新 @tarojs/* 版本
Object.keys(packageMap.dependencies).forEach((key) => {
if (UPDATE_PACKAGE_LIST.indexOf(key) !== -1) {
packageMap.dependencies[key] = version
}
})
Object.keys(packageMap.devDependencies).forEach((key) => {
if (UPDATE_PACKAGE_LIST.indexOf(key) !== -1) {
packageMap.devDependencies[key] = version
}
})
// 写入package.json
try {
await fs.writeJson(pkgPath, packageMap, {spaces: '\t'})
console.log(chalk.green('更新项目 package.json 成功!'))
console.log()
} catch (err) {
console.error(err)
}
let command
if (shouldUseYarn()) {
command = 'yarn'
} else if (shouldUseCnpm()) {
command = 'cnpm install'
} else {
command = 'npm install'
}
let child = exec(command)
const spinner = ora('即将将项目所有 Taro 相关依赖更新到最新版本...').start()
child.stdout.on('data', function (data) {
spinner.stop()
console.log(data)
})
child.stderr.on('data', function (data) {
spinner.stop()
console.log(data)
})
}

View File

@ -0,0 +1,9 @@
🏮 🎉 🏮 🎉 🏮 🎉 🏮 🎉 🏮 🎉 🏮 🎉 🏮 🎉 🏮 🎉 🏮 🎉 🏮 🎉 🏮 🎉 🏮 🎉 🏮 🎉 🏮 🎉 🏮
🎉 _ _ _ _ _____ _____ __ _____ _ 🎉
🏮 | | | | | | | / __ | _ / || _ | | | 🏮
🎉 | |_| | ___| | | ___ `' / /| |/' `| || |_| | | | 🎉
🏮 | _ |/ _ | | |/ _ \ / / | /| || |\____ | | | 🏮
🎉 | | | | __| | | (_) | ./ /__\ |_/ _| |.___/ / |_| 🎉
🏮 \_| |_/\___|_|_|\___/ \_____/\___/\___\____/ (_) 🏮
🎉 🎉
🏮 🎉 🏮 🎉 🏮 🎉 🏮 🎉 🏮 🎉 🏮 🎉 🏮 🎉 🏮 🎉 🏮 🎉 🏮 🎉 🏮 🎉 🏮 🎉 🏮 🎉 🏮 🎉 🏮

View File

@ -0,0 +1,87 @@
{
"name": "@tarojs/cli",
"version": "1.2.13",
"description": "cli tool for taro",
"main": "index.js",
"scripts": {
"test": "jest"
},
"repository": {
"type": "git",
"url": "git+https://github.com/NervJS/taro.git"
},
"bin": {
"taro": "bin/taro"
},
"keywords": [
"taro",
"weapp"
],
"engines": {
"node": ">=8"
},
"author": "O2Team",
"license": "MIT",
"dependencies": {
"@tarojs/taroize": "1.2.13",
"autoprefixer": "^8.4.1",
"babel-core": "^6.26.3",
"babel-generator": "^6.26.1",
"babel-plugin-danger-remove-unused-import": "^1.1.1",
"babel-plugin-remove-dead-code": "^1.3.2",
"babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-plugin-transform-define": "^1.3.0",
"babel-plugin-transform-jsx-to-stylesheet": "1.2.13",
"babel-plugin-transform-react-jsx": "^6.24.1",
"babel-template": "^6.26.0",
"babel-traverse": "^6.26.0",
"babel-types": "^6.26.0",
"babylon": "^6.18.0",
"better-babel-generator": "^6.26.1",
"chalk": "^2.3.2",
"chokidar": "^2.0.3",
"commander": "^2.15.0",
"cross-spawn": "^6.0.5",
"css-to-react-native-transform": "^1.4.0",
"ejs": "^2.6.1",
"envinfo": "^6.0.1",
"fs-extra": "^5.0.0",
"generic-names": "^2.0.1",
"glob": "^7.1.2",
"inquirer": "^5.2.0",
"klaw": "^2.1.1",
"latest-version": "^4.0.0",
"lodash": "^4.17.5",
"mem-fs": "^1.1.3",
"mem-fs-editor": "^4.0.0",
"minimatch": "^3.0.4",
"ora": "^2.0.0",
"postcss": "^6.0.22",
"postcss-modules-extract-imports": "^1.1.0",
"postcss-modules-local-by-default": "^1.2.0",
"postcss-modules-resolve-imports": "^1.3.0",
"postcss-modules-scope": "^1.1.0",
"postcss-modules-values": "^1.3.0",
"postcss-pxtransform": "1.2.13",
"postcss-taro-unit-transform": "1.2.13",
"postcss-url": "^7.3.2",
"prettier": "^1.14.3",
"prop-types": "^15.6.2",
"resolve": "^1.6.0",
"semver": "^5.5.0",
"shelljs": "^0.8.1",
"through2": "^2.0.3",
"vinyl": "^2.1.0",
"vinyl-fs": "^3.0.2"
},
"devDependencies": {
"babel-jest": "^23.6.0",
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-preset-env": "^1.7.0",
"babel-preset-react": "^6.24.1",
"babel-preset-stage-0": "^6.24.1",
"jest": "^23.6.0",
"jest-react-native": "^18.0.0",
"react-native": "^0.55.4"
}
}

View File

@ -0,0 +1,90 @@
const fs = require('fs-extra')
const path = require('path')
const chalk = require('chalk')
const _ = require('lodash')
const Util = require('./util')
const CONFIG = require('./config')
const appPath = process.cwd()
function build (args, buildConfig) {
const { type, watch } = buildConfig
const configDir = require(path.join(appPath, Util.PROJECT_CONFIG))(_.merge)
const outputPath = path.join(appPath, configDir.outputRoot || CONFIG.OUTPUT_DIR)
if (!fs.existsSync(outputPath)) {
fs.mkdirSync(outputPath)
} else {
if (type !== Util.BUILD_TYPES.H5) {
Util.emptyDirectory(outputPath)
}
}
switch (type) {
case Util.BUILD_TYPES.H5:
buildForH5({ watch })
break
case Util.BUILD_TYPES.WEAPP:
buildForWeapp({ watch })
break
case Util.BUILD_TYPES.SWAN:
buildForSwan({ watch })
break
case Util.BUILD_TYPES.ALIPAY:
buildForAlipay({ watch })
break
case Util.BUILD_TYPES.TT:
buildForTt({ watch })
break
case Util.BUILD_TYPES.RN:
buildForRN({ watch })
break
case Util.BUILD_TYPES.UI:
buildForUILibrary({ watch })
break
default:
console.log(chalk.red('输入类型错误,目前只支持 weapp/h5/rn/swan/alipay/tt 六端类型'))
}
}
function buildForWeapp ({ watch }) {
require('./weapp').build({
watch,
adapter: Util.BUILD_TYPES.WEAPP
})
}
function buildForSwan ({ watch }) {
require('./weapp').build({
watch,
adapter: Util.BUILD_TYPES.SWAN
})
}
function buildForAlipay ({ watch }) {
require('./weapp').build({
watch,
adapter: Util.BUILD_TYPES.ALIPAY
})
}
function buildForTt ({ watch }) {
require('./weapp').build({
watch,
adapter: Util.BUILD_TYPES.TT
})
}
function buildForH5 (buildConfig) {
require('./h5').build(buildConfig)
}
function buildForRN ({ watch }) {
require('./rn').build({ watch })
}
function buildForUILibrary ({ watch }) {
require('./ui').build({ watch })
}
module.exports = build

View File

@ -0,0 +1,12 @@
module.exports = {
sourceMap: true,
presets: [
'env'
],
plugins: [
require('babel-plugin-transform-react-jsx'),
'transform-decorators-legacy',
'transform-class-properties',
'transform-object-rest-spread'
]
}

View File

@ -0,0 +1,15 @@
module.exports = {
sourceType: 'module',
plugins: [
'typescript',
'classProperties',
'jsx',
'trailingFunctionCommas',
'asyncFunctions',
'exponentiationOperator',
'asyncGenerators',
'objectRestSpread',
'decorators',
'dynamicImport'
]
}

View File

@ -0,0 +1,5 @@
module.exports = [
'last 3 versions',
'Android >= 4.1',
'ios >= 8'
]

View File

@ -0,0 +1,7 @@
module.exports = {
OUTPUT_DIR: 'dist',
SOURCE_DIR: 'src',
TEMP_DIR: '.temp',
NPM_DIR: 'npm',
ENTRY: 'app'
}

View File

@ -0,0 +1,28 @@
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"baseUrl": ".",
"declaration": false,
"experimentalDecorators": true,
"jsx": "react",
"jsxFactory": "Nerv.createElement",
"module": "commonjs",
"moduleResolution": "node",
"noImplicitAny": false,
"noUnusedLocals": true,
"outDir": "./dist/",
"preserveConstEnums": true,
"removeComments": false,
"rootDir": ".",
"sourceMap": true,
"strictNullChecks": true,
"target": "es6"
},
"include": [
"src/**/*"
],
"exclude": [
"node_modules"
],
"compileOnSave": false
}

View File

@ -0,0 +1,3 @@
module.exports = {
}

View File

@ -0,0 +1,720 @@
const fs = require('fs-extra')
const path = require('path')
const chalk = require('chalk')
const prettier = require('prettier')
const traverse = require('babel-traverse').default
const t = require('babel-types')
const template = require('babel-template')
const taroize = require('@tarojs/taroize')
const wxTransformer = require('../../taro-transformer-wx/lib/src/index.js').default
const postcss = require('postcss')
const unitTransform = require('postcss-taro-unit-transform')
const {
BUILD_TYPES,
MINI_APP_FILES,
printLog,
pocessTypeEnum,
promoteRelativePath,
resolveScriptPath,
REG_SCRIPT,
REG_TYPESCRIPT,
processStyleImports,
getPkgVersion,
pascalCase,
emptyDirectory,
REG_URL,
REG_IMAGE
} = require('./util')
const { generateMinimalEscapeCode } = require('./util/ast_convert')
const Creator = require('./creator')
const babylonConfig = require('./config/babylon')
const prettierJSConfig = {
semi: false,
singleQuote: true,
parser: 'babel'
}
const OUTPUT_STYLE_EXTNAME = '.scss'
const WX_GLOBAL_FN = ['getApp', 'getCurrentPages', 'requirePlugin']
function analyzeImportUrl (sourceFilePath, scriptFiles, source, value) {
const valueExtname = path.extname(value)
if (path.isAbsolute(value)) {
printLog(pocessTypeEnum.ERROR, '引用文件', `文件 ${sourceFilePath} 中引用 ${value} 是绝对路径!`)
return
}
if (value.indexOf('.') === 0) {
if (REG_SCRIPT.test(valueExtname) || REG_TYPESCRIPT.test(valueExtname)) {
const vpath = path.resolve(sourceFilePath, '..', value)
let fPath = value
if (fs.existsSync(vpath)) {
fPath = vpath
} else {
printLog(pocessTypeEnum.ERROR, '引用文件', `文件 ${sourceFilePath} 中引用 ${value} 不存在!`)
}
scriptFiles.add(fPath)
} else {
let vpath = resolveScriptPath(path.resolve(sourceFilePath, '..', value))
if (vpath) {
if (!fs.existsSync(vpath)) {
printLog(pocessTypeEnum.ERROR, '引用文件', `文件 ${sourceFilePath} 中引用 ${value} 不存在!`)
} else {
if (fs.lstatSync(vpath).isDirectory()) {
if (fs.existsSync(path.join(vpath, 'index.js'))) {
vpath = path.join(vpath, 'index.js')
} else {
printLog(pocessTypeEnum.ERROR, '引用目录', `文件 ${sourceFilePath} 中引用了目录 ${value}`)
return
}
}
let relativePath = path.relative(sourceFilePath, vpath)
const relativePathExtname = path.extname(relativePath)
scriptFiles.add(vpath)
relativePath = promoteRelativePath(relativePath)
if (/\.wxs/.test(relativePathExtname)) {
relativePath += '.js'
} else {
relativePath = relativePath.replace(relativePathExtname, '.js')
}
source.value = relativePath
}
}
}
}
}
class Convertor {
constructor () {
this.root = process.cwd()
this.convertRoot = path.join(this.root, 'taroConvert')
this.convertDir = path.join(this.convertRoot, 'src')
this.importsDir = path.join(this.convertDir, 'imports')
this.fileTypes = MINI_APP_FILES[BUILD_TYPES.WEAPP]
this.pages = new Set()
this.components = new Set()
this.hadBeenCopyedFiles = new Set()
this.hadBeenBuiltComponents = new Set()
this.hadBeenBuiltImports = new Set()
this.init()
}
init () {
console.log(chalk.green('开始代码转换...'))
this.initConvert()
this.getApp()
this.getPages()
this.getSubPackages()
}
initConvert () {
if (fs.existsSync(this.convertRoot)) {
emptyDirectory(this.convertRoot, { excludes: ['node_modules'] })
} else {
fs.mkdirpSync(this.convertRoot)
}
}
parseAst ({ ast, sourceFilePath, outputFilePath, importStylePath, depComponents, imports = [], isApp = false }) {
const scriptFiles = new Set()
const self = this
let componentClassName = null
let needInsertImportTaro = false
traverse(ast, {
Program: {
enter (astPath) {
astPath.traverse({
ClassDeclaration (astPath) {
const node = astPath.node
let isTaroComponent = false
if (node.superClass) {
astPath.traverse({
ClassMethod (astPath) {
if (astPath.get('key').isIdentifier({ name: 'render' })) {
astPath.traverse({
JSXElement () {
isTaroComponent = true
}
})
}
}
})
if (isTaroComponent) {
componentClassName = node.id.name
}
}
},
ClassExpression (astPath) {
const node = astPath.node
if (node.superClass) {
let isTaroComponent = false
astPath.traverse({
ClassMethod (astPath) {
if (astPath.get('key').isIdentifier({ name: 'render' })) {
astPath.traverse({
JSXElement () {
isTaroComponent = true
}
})
}
}
})
if (isTaroComponent) {
if (node.id === null) {
const parentNode = astPath.parentPath.node
if (t.isVariableDeclarator(astPath.parentPath)) {
componentClassName = parentNode.id.name
}
} else {
componentClassName = node.id.name
}
}
}
},
ExportDefaultDeclaration (astPath) {
const node = astPath.node
const declaration = node.declaration
if (
declaration &&
(declaration.type === 'ClassDeclaration' || declaration.type === 'ClassExpression')
) {
const superClass = declaration.superClass
if (superClass) {
let isTaroComponent = false
astPath.traverse({
ClassMethod (astPath) {
if (astPath.get('key').isIdentifier({ name: 'render' })) {
astPath.traverse({
JSXElement () {
isTaroComponent = true
}
})
}
}
})
if (isTaroComponent) {
componentClassName = declaration.id.name
}
}
}
},
ImportDeclaration (astPath) {
const node = astPath.node
const source = node.source
const value = source.value
analyzeImportUrl(sourceFilePath, scriptFiles, source, value)
},
CallExpression (astPath) {
const node = astPath.node
const calleePath = astPath.get('callee')
const callee = calleePath.node
if (callee.type === 'Identifier') {
if (callee.name === 'require') {
const args = node.arguments
const value = args[0].value
analyzeImportUrl(sourceFilePath, scriptFiles, args[0], value)
} else if (WX_GLOBAL_FN.includes(callee.name)) {
calleePath.replaceWith(
t.memberExpression(t.identifier('Taro'), callee)
)
needInsertImportTaro = true
}
} else if (callee.type === 'MemberExpression') {
const object = callee.object
if (object.name === 'wx') {
calleePath.get('object').replaceWith(t.identifier('Taro'))
needInsertImportTaro = true
}
}
}
})
},
exit (astPath) {
const lastImport = astPath.get('body').filter(p => p.isImportDeclaration()).pop()
const hasTaroImport = astPath.get('body').some(p => p.isImportDeclaration() && p.node.source.value === '@tarojs/taro')
if (needInsertImportTaro && !hasTaroImport) {
astPath.node.body.unshift(
t.importDeclaration(
[t.importDefaultSpecifier(t.identifier('Taro'))],
t.stringLiteral('@tarojs/taro')
)
)
}
astPath.traverse({
StringLiteral (astPath) {
const value = astPath.node.value
const extname = path.extname(value)
if (extname && REG_IMAGE.test(extname) && !REG_URL.test(value)) {
let imageRelativePath = null
let sourceImagePath = null
let outputImagePath = null
if (path.isAbsolute(value)) {
sourceImagePath = path.join(self.root, value)
} else {
sourceImagePath = path.resolve(sourceFilePath, '..', value)
}
imageRelativePath = promoteRelativePath(path.relative(sourceFilePath, sourceImagePath))
outputImagePath = self.getDistFilePath(sourceImagePath)
if (fs.existsSync(sourceImagePath)) {
self.copyFileToTaro(sourceImagePath, outputImagePath)
printLog(pocessTypeEnum.COPY, '图片', self.generateShowPath(outputImagePath))
} else {
printLog(pocessTypeEnum.ERROR, '图片不存在', self.generateShowPath(sourceImagePath))
}
if (astPath.parentPath.isVariableDeclarator()) {
astPath.replaceWith(t.callExpression(t.identifier('require'), [t.stringLiteral(imageRelativePath)]))
} else if (astPath.parentPath.isJSXAttribute()) {
astPath.replaceWith(t.jSXExpressionContainer(t.callExpression(t.identifier('require'), [t.stringLiteral(imageRelativePath)])))
}
}
}
})
if (lastImport) {
if (importStylePath) {
lastImport.insertAfter(t.importDeclaration([], t.stringLiteral(promoteRelativePath(path.relative(sourceFilePath, importStylePath)))))
}
if (imports && imports.length) {
imports.forEach(({ name, ast }) => {
const importName = pascalCase(name)
if (componentClassName === importName) {
return
}
const importPath = path.join(self.importsDir, importName + '.js')
if (!self.hadBeenBuiltImports.has(importPath)) {
self.hadBeenBuiltImports.add(importPath)
self.writeFileToTaro(importPath, prettier.format(generateMinimalEscapeCode(ast), prettierJSConfig))
}
lastImport.insertAfter(template(`import ${importName} from '${promoteRelativePath(path.relative(outputFilePath, importPath))}'`, babylonConfig)())
})
}
if (depComponents && depComponents.size) {
depComponents.forEach(componentObj => {
const name = pascalCase(componentObj.name)
const component = componentObj.path
lastImport.insertAfter(template(`import ${name} from '${promoteRelativePath(path.relative(sourceFilePath, component))}'`, babylonConfig)())
})
}
if (isApp) {
astPath.node.body.push(template(`Taro.render(<App />, document.getElementById('app'))`, babylonConfig)())
}
}
}
}
})
return {
ast,
scriptFiles
}
}
getApp () {
this.entryJSPath = path.join(this.root, `app${this.fileTypes.SCRIPT}`)
this.entryJSONPath = path.join(this.root, `app${this.fileTypes.CONFIG}`)
this.entryStylePath = path.join(this.root, `app${this.fileTypes.STYLE}`)
try {
this.entryJSON = JSON.parse(String(fs.readFileSync(this.entryJSONPath)))
printLog(pocessTypeEnum.CONVERT, '入口文件', this.generateShowPath(this.entryJSPath))
printLog(pocessTypeEnum.CONVERT, '入口配置', this.generateShowPath(this.entryJSONPath))
if (fs.existsSync(this.entryStylePath)) {
this.entryStyle = String(fs.readFileSync(this.entryStylePath))
printLog(pocessTypeEnum.CONVERT, '入口样式', this.generateShowPath(this.entryStylePath))
}
} catch (err) {
this.entryJSON = {}
console.log(chalk.red(`app${this.fileTypes.CONFIG} 读取失败,请检查!`))
process.exit(1)
}
}
getPages () {
const pages = this.entryJSON['pages']
if (!pages || !pages.length) {
console.log(chalk.red(`app${this.fileTypes.CONFIG} 配置有误,缺少页面相关配置`))
return
}
this.pages = new Set(pages)
}
getSubPackages () {
const subPackages = this.entryJSON['subpackages'] || this.entryJSON['subPackages']
if (!subPackages || !subPackages.length) {
return
}
subPackages.forEach(item => {
if (item.pages && item.pages.length) {
const root = item.root
item.pages.forEach(page => {
let pagePath = `${root}/${page}`
pagePath = pagePath.replace(/\/{2,}/g, '/')
this.pages.add(pagePath)
})
}
})
}
generateScriptFiles (files) {
if (!files) {
return
}
if (files.size) {
files.forEach(file => {
if (!fs.existsSync(file) || this.hadBeenCopyedFiles.has(file)) {
return
}
const code = fs.readFileSync(file).toString()
let outputFilePath = file.replace(this.root, this.convertDir)
const extname = path.extname(outputFilePath)
if (/\.wxs/.test(extname)) {
outputFilePath += '.js'
}
const transformResult = wxTransformer({
code,
sourcePath: file,
outputPath: outputFilePath,
isNormal: true,
isTyped: REG_TYPESCRIPT.test(file)
})
const { ast, scriptFiles } = this.parseAst({
ast: transformResult.ast,
outputFilePath,
sourceFilePath: file
})
const jsCode = generateMinimalEscapeCode(ast)
this.writeFileToTaro(outputFilePath, prettier.format(jsCode, prettierJSConfig))
printLog(pocessTypeEnum.COPY, 'JS 文件', this.generateShowPath(outputFilePath))
this.hadBeenCopyedFiles.add(file)
this.generateScriptFiles(scriptFiles)
})
}
}
writeFileToTaro (dist, code) {
fs.ensureDirSync(path.dirname(dist))
fs.writeFileSync(dist, code)
}
copyFileToTaro (from, to, options) {
const filename = path.basename(from)
if (fs.statSync(from).isFile() && !path.extname(to)) {
fs.ensureDir(to)
return fs.copySync(from, path.join(to, filename), options)
}
fs.ensureDir(path.dirname(to))
return fs.copySync(from, to, options)
}
getDistFilePath (src, extname) {
if (!extname) return src.replace(this.root, this.convertDir)
return src.replace(this.root, this.convertDir).replace(path.extname(src), extname)
}
generateShowPath (filePath) {
return filePath.replace(path.join(this.root, '/'), '').split(path.sep).join('/')
}
generateEntry () {
try {
const entryJS = String(fs.readFileSync(this.entryJSPath))
const entryJSON = JSON.stringify(this.entryJSON)
const entryDistJSPath = this.getDistFilePath(this.entryJSPath)
const taroizeResult = taroize({
json: entryJSON,
script: entryJS,
path: path.dirname(entryJS)
})
const { ast, scriptFiles } = this.parseAst({
ast: taroizeResult.ast,
sourceFilePath: this.entryJSPath,
outputFilePath: entryDistJSPath,
importStylePath: this.entryStyle ? this.entryStylePath.replace(path.extname(this.entryStylePath), OUTPUT_STYLE_EXTNAME) : null,
isApp: true
})
const jsCode = generateMinimalEscapeCode(ast)
this.writeFileToTaro(entryDistJSPath, prettier.format(jsCode, prettierJSConfig))
printLog(pocessTypeEnum.GENERATE, '入口文件', this.generateShowPath(entryDistJSPath))
if (this.entryStyle) {
this.traverseStyle(this.entryStylePath, this.entryStyle)
}
this.generateScriptFiles(scriptFiles)
if (this.entryJSON.tabBar) {
this.generateTabBarIcon(this.entryJSON.tabBar)
}
} catch (err) {
console.log(err)
}
}
generateTabBarIcon (tabBar) {
const { list = [] } = tabBar
const icons = new Set()
if (Array.isArray(list) && list.length) {
list.forEach(item => {
if (typeof item.iconPath === 'string') icons.add(item.iconPath)
if (typeof item.selectedIconPath === 'string') icons.add(item.selectedIconPath)
})
if (icons.size > 0) {
Array.from(icons)
.map(icon => path.join(this.root, icon))
.forEach(iconPath => {
const iconDistPath = this.getDistFilePath(iconPath)
this.copyFileToTaro(iconPath, iconDistPath)
printLog(pocessTypeEnum.COPY, 'TabBar 图标', this.generateShowPath(iconDistPath))
})
}
}
}
traversePages () {
this.pages.forEach(page => {
const pagePath = path.join(this.root, page)
const pageJSPath = pagePath + this.fileTypes.SCRIPT
const pageDistJSPath = this.getDistFilePath(pageJSPath)
const pageConfigPath = pagePath + this.fileTypes.CONFIG
const pageStylePath = pagePath + this.fileTypes.STYLE
const pageTemplPath = pagePath + this.fileTypes.TEMPL
try {
const param = {}
const depComponents = new Set()
if (!fs.existsSync(pageJSPath)) {
throw new Error(`页面 ${page} 没有 JS 文件!`)
}
printLog(pocessTypeEnum.CONVERT, '页面文件', this.generateShowPath(pageJSPath))
if (fs.existsSync(pageConfigPath)) {
printLog(pocessTypeEnum.CONVERT, '页面配置', this.generateShowPath(pageConfigPath))
const pageConfigStr = String(fs.readFileSync(pageConfigPath))
const pageConfig = JSON.parse(pageConfigStr)
const pageUsingComponnets = pageConfig.usingComponents
if (pageUsingComponnets) {
// 页面依赖组件
Object.keys(pageUsingComponnets).forEach(component => {
let componentPath = path.resolve(pageConfigPath, '..', pageUsingComponnets[component])
if (!fs.existsSync(resolveScriptPath(componentPath))) {
componentPath = path.join(this.root, pageUsingComponnets[component])
}
depComponents.add({
name: component,
path: componentPath
})
})
delete pageConfig.usingComponents
}
param.json = JSON.stringify(pageConfig)
}
param.script = String(fs.readFileSync(pageJSPath))
if (fs.existsSync(pageTemplPath)) {
printLog(pocessTypeEnum.CONVERT, '页面模板', this.generateShowPath(pageTemplPath))
param.wxml = String(fs.readFileSync(pageTemplPath))
}
let pageStyle = null
if (fs.existsSync(pageStylePath)) {
printLog(pocessTypeEnum.CONVERT, '页面样式', this.generateShowPath(pageStylePath))
pageStyle = String(fs.readFileSync(pageStylePath))
}
param.path = path.dirname(pageJSPath)
const taroizeResult = taroize(param)
const { ast, scriptFiles } = this.parseAst({
ast: taroizeResult.ast,
sourceFilePath: pageJSPath,
outputFilePath: pageDistJSPath,
importStylePath: pageStyle ? pageStylePath.replace(path.extname(pageStylePath), OUTPUT_STYLE_EXTNAME) : null,
depComponents,
imports: taroizeResult.imports
})
const jsCode = generateMinimalEscapeCode(ast)
this.writeFileToTaro(pageDistJSPath, prettier.format(jsCode, prettierJSConfig))
printLog(pocessTypeEnum.GENERATE, '页面文件', this.generateShowPath(pageDistJSPath))
if (pageStyle) {
this.traverseStyle(pageStylePath, pageStyle)
}
this.generateScriptFiles(scriptFiles)
this.traverseComponents(depComponents)
} catch (err) {
printLog(pocessTypeEnum.ERROR, '页面转换', this.generateShowPath(pageJSPath))
console.log(err)
}
})
}
traverseComponents (components) {
if (!components || !components.size) {
return
}
components.forEach(componentObj => {
const component = componentObj.path
if (this.hadBeenBuiltComponents.has(component)) return
this.hadBeenBuiltComponents.add(component)
const componentJSPath = component + this.fileTypes.SCRIPT
const componentDistJSPath = this.getDistFilePath(componentJSPath)
const componentConfigPath = component + this.fileTypes.CONFIG
const componentStylePath = component + this.fileTypes.STYLE
const componentTemplPath = component + this.fileTypes.TEMPL
try {
const param = {}
const depComponents = new Set()
if (!fs.existsSync(componentJSPath)) {
throw new Error(`组件 ${component} 没有 JS 文件!`)
}
printLog(pocessTypeEnum.CONVERT, '组件文件', this.generateShowPath(componentJSPath))
if (fs.existsSync(componentConfigPath)) {
printLog(pocessTypeEnum.CONVERT, '组件配置', this.generateShowPath(componentConfigPath))
const componentConfigStr = String(fs.readFileSync(componentConfigPath))
const componentConfig = JSON.parse(componentConfigStr)
const componentUsingComponnets = componentConfig.usingComponents
if (componentUsingComponnets) {
// 页面依赖组件
Object.keys(componentUsingComponnets).forEach(component => {
let componentPath = path.resolve(componentConfigPath, '..', componentUsingComponnets[component])
if (!fs.existsSync(resolveScriptPath(componentPath))) {
componentPath = path.join(this.root, componentUsingComponnets[component])
}
depComponents.add({
name: component,
path: componentPath
})
})
delete componentConfig.usingComponents
}
param.json = JSON.stringify(componentConfig)
}
param.script = String(fs.readFileSync(componentJSPath))
if (fs.existsSync(componentTemplPath)) {
printLog(pocessTypeEnum.CONVERT, '组件模板', this.generateShowPath(componentTemplPath))
param.wxml = String(fs.readFileSync(componentTemplPath))
}
let componentStyle = null
if (fs.existsSync(componentStylePath)) {
printLog(pocessTypeEnum.CONVERT, '组件样式', this.generateShowPath(componentStylePath))
componentStyle = String(fs.readFileSync(componentStylePath))
}
param.path = path.dirname(componentJSPath)
const taroizeResult = taroize(param)
const { ast, scriptFiles } = this.parseAst({
ast: taroizeResult.ast,
sourceFilePath: componentJSPath,
outputFilePath: componentDistJSPath,
importStylePath: componentStyle ? componentStylePath.replace(path.extname(componentStylePath), OUTPUT_STYLE_EXTNAME) : null,
depComponents,
imports: taroizeResult.imports
})
const jsCode = generateMinimalEscapeCode(ast)
this.writeFileToTaro(componentDistJSPath, prettier.format(jsCode, prettierJSConfig))
printLog(pocessTypeEnum.GENERATE, '组件文件', this.generateShowPath(componentDistJSPath))
if (componentStyle) {
this.traverseStyle(componentStylePath, componentStyle)
}
this.generateScriptFiles(scriptFiles)
this.traverseComponents(depComponents)
} catch (err) {
printLog(pocessTypeEnum.ERROR, '组件转换', this.generateShowPath(componentJSPath))
console.log(err)
}
})
}
async styleUnitTransform (filePath, content) {
const postcssResult = await postcss([
unitTransform()
]).process(content, {
from: filePath
})
return postcssResult
}
async traverseStyle (filePath, style) {
const { imports, content } = processStyleImports(style, BUILD_TYPES.WEAPP, (str, stylePath) => {
let relativePath = stylePath
if (path.isAbsolute(relativePath)) {
relativePath = promoteRelativePath(path.relative(filePath, path.join(this.root, stylePath)))
}
return str.replace(stylePath, relativePath)
.replace(MINI_APP_FILES[BUILD_TYPES.WEAPP].STYLE, OUTPUT_STYLE_EXTNAME)
})
const styleDist = this.getDistFilePath(filePath, OUTPUT_STYLE_EXTNAME)
const { css } = await this.styleUnitTransform(filePath, content)
this.writeFileToTaro(styleDist, css)
printLog(pocessTypeEnum.GENERATE, '样式文件', this.generateShowPath(styleDist))
if (imports && imports.length) {
imports.forEach(importItem => {
const importPath = path.isAbsolute(importItem)
? path.join(this.root, importItem)
: path.resolve(path.dirname(filePath), importItem)
if (fs.existsSync(importPath)) {
const styleText = fs.readFileSync(importPath).toString()
this.traverseStyle(importPath, styleText)
}
})
}
}
generateConfigFiles () {
const creator = new Creator()
const templateName = 'default'
const configDir = path.join(this.convertRoot, 'config')
const pkgPath = path.join(this.convertRoot, 'package.json')
const projectName = 'taroConvert'
const description = ''
const version = getPkgVersion()
const dateObj = new Date()
const date = `${dateObj.getFullYear()}-${(dateObj.getMonth() + 1)}-${dateObj.getDate()}`
creator.template(templateName, 'pkg', pkgPath, {
description,
projectName,
version,
css: 'sass',
typescript: false
})
creator.template(templateName, path.join('config', 'index'), path.join(configDir, 'index.js'), {
date,
projectName
})
creator.template(templateName, path.join('config', 'dev'), path.join(configDir, 'dev.js'))
creator.template(templateName, path.join('config', 'prod'), path.join(configDir, 'prod.js'))
creator.template(templateName, 'project', path.join(this.convertRoot, 'project.config.json'), {
description,
projectName
})
creator.template(templateName, 'gitignore', path.join(this.convertRoot, '.gitignore'))
creator.template(templateName, 'editorconfig', path.join(this.convertRoot, '.editorconfig'))
creator.template(templateName, 'eslintrc', path.join(this.convertRoot, '.eslintrc'), {
typescript: false
})
creator.template(templateName, 'indexhtml', path.join(this.convertDir, 'index.html'))
creator.fs.commit(() => {
const pkgObj = JSON.parse(fs.readFileSync(pkgPath).toString())
pkgObj.dependencies['@tarojs/with-weapp'] = `^${version}`
fs.writeJSONSync(pkgPath, pkgObj, {
spaces: 2,
EOL: '\n'
})
printLog(pocessTypeEnum.GENERATE, '文件', this.generateShowPath(path.join(configDir, 'index.js')))
printLog(pocessTypeEnum.GENERATE, '文件', this.generateShowPath(path.join(configDir, 'dev.js')))
printLog(pocessTypeEnum.GENERATE, '文件', this.generateShowPath(path.join(configDir, 'prod.js')))
printLog(pocessTypeEnum.GENERATE, '文件', this.generateShowPath(pkgPath))
printLog(pocessTypeEnum.GENERATE, '文件', this.generateShowPath(path.join(this.convertRoot, 'project.config.json')))
printLog(pocessTypeEnum.GENERATE, '文件', this.generateShowPath(path.join(this.convertRoot, '.gitignore')))
printLog(pocessTypeEnum.GENERATE, '文件', this.generateShowPath(path.join(this.convertRoot, '.editorconfig')))
printLog(pocessTypeEnum.GENERATE, '文件', this.generateShowPath(path.join(this.convertRoot, '.eslintrc')))
printLog(pocessTypeEnum.GENERATE, '文件', this.generateShowPath(path.join(this.convertDir, 'index.html')))
})
}
run () {
this.generateEntry()
this.traversePages()
this.generateConfigFiles()
}
}
module.exports = Convertor

View File

@ -0,0 +1,86 @@
const path = require('path')
const fs = require('fs-extra')
const memFs = require('mem-fs')
const editor = require('mem-fs-editor')
const {
getRootPath
} = require('./util')
class Creator {
constructor () {
const store = memFs.create()
this.fs = editor.create(store)
this.sourceRoot(path.join(getRootPath()))
this.init()
}
init () {}
sourceRoot (rootPath) {
if (typeof rootPath === 'string') {
this._rootPath = path.resolve(rootPath)
}
if (!fs.existsSync(this._rootPath)) {
fs.ensureDirSync(this._rootPath)
}
return this._rootPath
}
templatePath () {
let filepath = path.join.apply(path, arguments)
if (!path.isAbsolute(filepath)) {
filepath = path.join(this._rootPath, 'templates', filepath)
}
return filepath
}
destinationRoot (rootPath) {
if (typeof rootPath === 'string') {
this._destinationRoot = path.resolve(rootPath)
if (!fs.existsSync(rootPath)) {
fs.ensureDirSync(rootPath)
}
process.chdir(rootPath)
}
return this._destinationRoot || process.cwd()
}
destinationPath () {
let filepath = path.join.apply(path, arguments)
if (!path.isAbsolute(filepath)) {
filepath = path.join(this.destinationRoot(), filepath)
}
return filepath
}
template (template, source, dest, data, options) {
if (typeof dest !== 'string') {
options = data
data = dest
dest = source
}
this.fs.copyTpl(
this.templatePath(template, source),
this.destinationPath(dest),
Object.assign({}, this, data),
options
)
return this
}
copy (template, type, source, dest) {
dest = dest || source
this.template(template, type, source, dest)
return this
}
writeGitKeepFile (dirname) {
dirname = path.resolve(dirname)
fs.writeFileSync(path.join(dirname, '.gitkeep'), 'Place hold file', 'utf8')
}
write () {}
}
module.exports = Creator

View File

@ -0,0 +1,11 @@
var assign = function (s, d) {
if (typeof s !== 'object') {
return d
}
s = s || {}
d = d || {}
return JSON.parse((JSON.stringify(s) + JSON.stringify(d)).replace('}{', ','))
}
module.exports = {
assign: assign
};

View File

@ -0,0 +1,978 @@
const fs = require('fs-extra')
const path = require('path')
const chokidar = require('chokidar')
const wxTransformer = require('../../taro-transformer-wx/lib/src/index.js').default
const klaw = require('klaw')
const traverse = require('babel-traverse').default
const t = require('babel-types')
const babel = require('babel-core')
const generate = require('better-babel-generator').default
const _ = require('lodash')
const rimraf = require('rimraf')
const { promisify } = require('util')
const minimatch = require('minimatch')
const Util = require('./util')
const npmProcess = require('./util/npm')
const CONFIG = require('./config')
const { source: toAst, getObjKey } = require('./util/ast_convert')
const appPath = process.cwd()
const projectConfig = require(path.join(appPath, Util.PROJECT_CONFIG))(_.merge)
const h5Config = projectConfig.h5 || {}
const routerConfig = h5Config.router || {}
const routerMode = routerConfig.mode === 'browser' ? 'browser' : 'hash'
const customRoutes = routerConfig.customRoutes || {}
const routerBasename = routerConfig.basename || '/'
const sourceDir = projectConfig.sourceRoot || CONFIG.SOURCE_DIR
const sourcePath = path.join(appPath, sourceDir)
const outputDir = projectConfig.outputRoot || CONFIG.OUTPUT_DIR
const outputPath = path.join(appPath, outputDir)
const tempDir = CONFIG.TEMP_DIR
const tempPath = path.join(appPath, tempDir)
const entryFilePath = Util.resolveScriptPath(path.join(sourcePath, CONFIG.ENTRY))
const entryFileName = path.basename(entryFilePath)
let pxTransformConfig = { designWidth: projectConfig.designWidth || 750 }
const pathAlias = projectConfig.alias || {}
const PACKAGES = {
'@tarojs/taro': '@tarojs/taro',
'@tarojs/taro-h5': '@tarojs/taro-h5',
'@tarojs/redux': '@tarojs/redux',
'@tarojs/redux-h5': '@tarojs/redux-h5',
'@tarojs/mobx': '@tarojs/mobx',
'@tarojs/mobx-h5': '@tarojs/mobx-h5',
'@tarojs/router': `@tarojs/router`,
'@tarojs/components': '@tarojs/components',
'nervjs': 'nervjs',
'nerv-redux': 'nerv-redux'
}
const taroApis = [
'Component',
'getEnv',
'ENV_TYPE',
'eventCenter',
'Events',
'internal_safe_get',
'internal_dynamic_recursive'
]
const nervJsImportDefaultName = 'Nerv'
const tabBarComponentName = 'Tabbar'
const tabBarContainerComponentName = 'TabbarContainer'
const tabBarPanelComponentName = 'TabbarPanel'
const providerComponentName = 'Provider'
const setStoreFuncName = 'setStore'
const tabBarConfigName = '__tabs'
const DEVICE_RATIO = 'deviceRatio'
if (projectConfig.hasOwnProperty(DEVICE_RATIO)) {
pxTransformConfig[DEVICE_RATIO] = projectConfig.deviceRatio
}
let pages = []
let tabBar
let tabbarPos
const FILE_TYPE = {
ENTRY: 'ENTRY',
PAGE: 'PAGE',
COMPONENT: 'COMPONENT',
NORMAL: 'NORMAL'
}
const addLeadingSlash = path => path.charAt(0) === '/' ? path : '/' + path
const stripTrailingSlash = path => path.charAt(path.length - 1) === '/' ? path.slice(0, -1) : path
const isUnderSubPackages = (parentPath) => (parentPath.isObjectProperty() && /subPackages|subpackages/i.test(getObjKey(parentPath.node.key)))
const publicPath = h5Config.publicPath
? stripTrailingSlash(addLeadingSlash(h5Config.publicPath || ''))
: ''
function createRoute ({ absPagename, relPagename, isIndex, chunkName = '' }) {
const chunkNameComment = chunkName ? `/* webpackChunkName: "${chunkName}" */` : ''
return `{
path: '${absPagename}',
componentLoader: () => import(${chunkNameComment}'${relPagename}'),
isIndex: ${isIndex}
}`
}
function processEntry (code, filePath) {
let ast = wxTransformer({
code,
sourcePath: filePath,
isNormal: true,
isTyped: Util.REG_TYPESCRIPT.test(filePath),
adapter: 'h5'
}).ast
let taroImportDefaultName
let providorImportName
let storeName
let renderCallCode
let hasAddNervJsImportDefaultName = false
let hasConstructor = false
let hasComponentWillMount = false
let hasComponentDidMount = false
let hasComponentDidShow = false
let hasComponentDidHide = false
let hasComponentWillUnmount = false
let hasJSX = false
let hasState = false
const initPxTransformNode = toAst(`Taro.initPxTransform(${JSON.stringify(pxTransformConfig)})`)
const additionalConstructorNode = toAst(`Taro._set$app(this)`)
ast = babel.transformFromAst(ast, '', {
plugins: [
[require('babel-plugin-danger-remove-unused-import'), { ignore: ['@tarojs/taro', 'react', 'nervjs'] }]
]
}).ast
const ClassDeclarationOrExpression = {
enter (astPath) {
const node = astPath.node
if (!node.superClass) return
if (
node.superClass.type === 'MemberExpression' &&
node.superClass.object.name === taroImportDefaultName
) {
node.superClass.object.name = taroImportDefaultName
if (node.id === null) {
const renameComponentClassName = '_TaroComponentClass'
astPath.replaceWith(
t.classExpression(
t.identifier(renameComponentClassName),
node.superClass,
node.body,
node.decorators || []
)
)
}
} else if (node.superClass.name === 'Component') {
resetTSClassProperty(node.body.body)
if (node.id === null) {
const renameComponentClassName = '_TaroComponentClass'
astPath.replaceWith(
t.classExpression(
t.identifier(renameComponentClassName),
node.superClass,
node.body,
node.decorators || []
)
)
}
}
}
}
/**
* ProgramExit使用的visitor
* 负责修改render函数的内容在componentDidMount中增加componentDidShow调用在componentWillUnmount中增加componentDidHide调用
*/
const programExitVisitor = {
ClassMethod: {
exit (astPath) {
const node = astPath.node
const key = node.key
const keyName = getObjKey(key)
let funcBody
const isRender = keyName === 'render'
const isComponentWillMount = keyName === 'componentWillMount'
const isComponentDidMount = keyName === 'componentDidMount'
const isComponentWillUnmount = keyName === 'componentWillUnmount'
const isConstructor = keyName === 'constructor'
const basename = JSON.stringify(addLeadingSlash(stripTrailingSlash(routerBasename)))
if (isRender) {
const routes = pages.map((v, k) => {
const absPagename = addLeadingSlash(v)
const relPagename = `.${absPagename}`
const chunkName = relPagename.split('/').filter(v => !/^(pages|\.)$/i.test(v)).join('_')
return createRoute({
absPagename,
relPagename,
chunkName,
isIndex: k === 0
})
})
funcBody = `<Router
mode={${JSON.stringify(routerMode)}}
publicPath={${JSON.stringify(routerMode === 'hash' ? '/' : publicPath)}}
routes={[${routes.join(',')}]}
customRoutes={${JSON.stringify(customRoutes)}}
basename={${basename}}
/>`
/* 插入Tabbar */
if (tabBar) {
const homePage = pages[0] || ''
if (tabbarPos === 'top') {
funcBody = `
<${tabBarContainerComponentName}>
<${tabBarComponentName} conf={${tabBarConfigName}} homePage="${homePage}" router={${taroImportDefaultName}}/>
<${tabBarPanelComponentName}>
${funcBody}
</${tabBarPanelComponentName}>
</${tabBarContainerComponentName}>`
} else {
funcBody = `
<${tabBarContainerComponentName}>
<${tabBarPanelComponentName}>
${funcBody}
</${tabBarPanelComponentName}>
<${tabBarComponentName}
mode={${JSON.stringify(routerMode)}}
conf={this.state.${tabBarConfigName}}
homePage="${homePage}"
router={${taroImportDefaultName}}
basename={${basename}} />
</${tabBarContainerComponentName}>`
}
}
/* 插入<Provider /> */
if (providerComponentName && storeName) {
// 使用redux 或 mobx
funcBody = `
<${providorImportName} store={${storeName}}>
${funcBody}
</${providorImportName}>`
}
/* 插入<Router /> */
node.body = toAst(`{return (${funcBody});}`, { preserveComments: true })
}
if (tabBar && isComponentWillMount) {
const initTabBarApisCallNode = toAst(`Taro.initTabBarApis(this, Taro)`)
astPath.get('body').pushContainer('body', initTabBarApisCallNode)
}
if (hasConstructor && isConstructor) {
astPath.get('body').pushContainer('body', additionalConstructorNode)
}
if (hasComponentDidShow && isComponentDidMount) {
const componentDidShowCallNode = toAst(`this.componentDidShow()`)
astPath.get('body').pushContainer('body', componentDidShowCallNode)
}
if (hasComponentDidHide && isComponentWillUnmount) {
const componentDidHideCallNode = toAst(`this.componentDidHide()`)
astPath.get('body').unshiftContainer('body', componentDidHideCallNode)
}
}
},
ClassProperty: {
exit (astPath) {
const node = astPath.node
const key = node.key
const value = node.value
if (key.name !== 'state' || !t.isObjectExpression(value)) return
astPath.node.value.properties.push(t.objectProperty(
t.identifier(tabBarConfigName),
tabBar
))
}
},
ClassBody: {
exit (astPath) {
if (hasComponentDidShow && !hasComponentDidMount) {
astPath.pushContainer('body', t.classMethod(
'method', t.identifier('componentDidMount'), [],
t.blockStatement([]), false, false))
}
if (hasComponentDidHide && !hasComponentWillUnmount) {
astPath.pushContainer('body', t.classMethod(
'method', t.identifier('componentWillUnmount'), [],
t.blockStatement([]), false, false))
}
if (!hasConstructor) {
astPath.pushContainer('body', t.classMethod(
'method', t.identifier('constructor'), [t.identifier('props'), t.identifier('context')],
t.blockStatement([toAst('super(props, context)'), additionalConstructorNode]), false, false))
}
if (tabBar) {
if (!hasComponentWillMount) {
astPath.pushContainer('body', t.classMethod(
'method', t.identifier('componentWillMount'), [],
t.blockStatement([]), false, false))
}
if (!hasState) {
astPath.unshiftContainer('body', t.classProperty(
t.identifier('state'),
t.objectExpression([])
))
}
}
}
}
}
/**
* ClassProperty使用的visitor
* 负责收集config中的pages收集tabbar的position替换icon
*/
const classPropertyVisitor = {
ObjectProperty (astPath) {
const node = astPath.node
const key = node.key
const value = node.value
const keyName = getObjKey(key)
if (keyName === 'pages' && t.isArrayExpression(value)) {
const subPackageParent = astPath.findParent(isUnderSubPackages)
let root = ''
if (subPackageParent) {
/* 在subPackages属性下说明是分包页面需要处理root属性 */
const rootNode = astPath.parent.properties.find(v => {
return getObjKey(v.key) === 'root'
})
root = rootNode ? rootNode.value.value : ''
}
value.elements.forEach(v => {
const pagePath = `${root}/${v.value}`.replace(/\/{2,}/g, '/')
pages.push(pagePath.replace(/^\//, ''))
})
} else if (keyName === 'tabBar' && t.isObjectExpression(value)) {
// tabBar
tabBar = value
value.properties.forEach(node => {
if (node.keyName === 'position') tabbarPos = node.value.value
})
} else if ((keyName === 'iconPath' || keyName === 'selectedIconPath') && t.isStringLiteral(value)) {
astPath.replaceWith(
t.objectProperty(t.stringLiteral(keyName), t.callExpression(t.identifier('require'), [t.stringLiteral(`./${value.value}`)]))
)
}
}
}
traverse(ast, {
ClassExpression: ClassDeclarationOrExpression,
ClassDeclaration: ClassDeclarationOrExpression,
ClassProperty: {
enter (astPath) {
const node = astPath.node
const key = node.key
const value = node.value
const keyName = getObjKey(key)
if (keyName === 'state') hasState = true
if (keyName !== 'config' || !t.isObjectExpression(value)) return
astPath.traverse(classPropertyVisitor)
}
},
ImportDeclaration: {
enter (astPath) {
const node = astPath.node
const source = node.source
const specifiers = node.specifiers
let value = source.value
if (Util.isAliasPath(value, pathAlias)) {
source.value = value = Util.replaceAliasPath(filePath, value, pathAlias)
}
if (!Util.isNpmPkg(value)) {
if (value.indexOf('.') === 0) {
const pathArr = value.split('/')
if (pathArr.indexOf('pages') >= 0) {
astPath.remove()
} else if (Util.REG_SCRIPTS.test(value)) {
const realPath = path.resolve(filePath, '..', value)
const dirname = path.dirname(realPath)
const extname = path.extname(realPath)
const removeExtPath = path.join(dirname, path.basename(realPath, extname))
node.source = t.stringLiteral(Util.promoteRelativePath(path.relative(filePath, removeExtPath)).replace(/\\/g, '/'))
}
}
return
}
if (value === PACKAGES['@tarojs/taro']) {
let specifier = specifiers.find(item => item.type === 'ImportDefaultSpecifier')
if (specifier) {
hasAddNervJsImportDefaultName = true
taroImportDefaultName = specifier.local.name
specifier.local.name = nervJsImportDefaultName
} else if (!hasAddNervJsImportDefaultName) {
hasAddNervJsImportDefaultName = true
node.specifiers.unshift(
t.importDefaultSpecifier(t.identifier(nervJsImportDefaultName))
)
}
const taroApisSpecifiers = []
const deletedIdx = []
specifiers.forEach((item, index) => {
if (item.imported && taroApis.indexOf(item.imported.name) >= 0) {
taroApisSpecifiers.push(t.importSpecifier(t.identifier(item.local.name), t.identifier(item.imported.name)))
deletedIdx.push(index)
}
})
_.pullAt(specifiers, deletedIdx)
source.value = PACKAGES['nervjs']
if (taroApisSpecifiers.length) {
astPath.insertBefore(t.importDeclaration(taroApisSpecifiers, t.stringLiteral(PACKAGES['@tarojs/taro-h5'])))
}
if (!specifiers.length) {
astPath.remove()
}
} else if (value === PACKAGES['@tarojs/redux']) {
const specifier = specifiers.find(item => {
return t.isImportSpecifier(item) && item.imported.name === providerComponentName
})
if (specifier) {
providorImportName = specifier.local.name
} else {
providorImportName = providerComponentName
specifiers.push(t.importSpecifier(t.identifier(providerComponentName), t.identifier(providerComponentName)))
}
source.value = PACKAGES['@tarojs/redux-h5']
} else if (value === PACKAGES['@tarojs/mobx']) {
const specifier = specifiers.find(item => {
return t.isImportSpecifier(item) && item.imported.name === providerComponentName
})
if (specifier) {
providorImportName = specifier.local.name
} else {
providorImportName = providerComponentName
specifiers.push(t.importSpecifier(t.identifier(providerComponentName), t.identifier(providerComponentName)))
}
source.value = PACKAGES['@tarojs/mobx-h5']
}
}
},
CallExpression: {
enter (astPath) {
const node = astPath.node
const callee = node.callee
const calleeName = callee.name
const parentPath = astPath.parentPath
if (t.isMemberExpression(callee)) {
if (callee.object.name === taroImportDefaultName && callee.property.name === 'render') {
callee.object.name = nervJsImportDefaultName
renderCallCode = generate(astPath.node).code
astPath.remove()
}
} else {
if (calleeName === setStoreFuncName) {
if (parentPath.isAssignmentExpression() ||
parentPath.isExpressionStatement() ||
parentPath.isVariableDeclarator()) {
parentPath.remove()
}
}
}
}
},
ClassMethod: {
exit (astPath) {
const node = astPath.node
const key = node.key
const keyName = getObjKey(key)
if (keyName === 'constructor') {
hasConstructor = true
} else if (keyName === 'componentWillMount') {
hasComponentWillMount = true
} else if (keyName === 'componentDidMount') {
hasComponentDidMount = true
} else if (keyName === 'componentDidShow') {
hasComponentDidShow = true
} else if (keyName === 'componentDidHide') {
hasComponentDidHide = true
} else if (keyName === 'componentWillUnmount') {
hasComponentWillUnmount = true
}
}
},
JSXElement: {
enter (astPath) {
hasJSX = true
}
},
JSXOpeningElement: {
enter (astPath) {
if (astPath.node.name.name === 'Provider') {
for (let v of astPath.node.attributes) {
if (v.name.name !== 'store') continue
storeName = v.value.expression.name
break
}
}
}
},
Program: {
exit (astPath) {
const importNervjsNode = t.importDefaultSpecifier(t.identifier(nervJsImportDefaultName))
const importRouterNode = toAst(`import { Router } from '${PACKAGES['@tarojs/router']}'`)
const importTaroH5Node = toAst(`import ${taroImportDefaultName} from '${PACKAGES['@tarojs/taro-h5']}'`)
const importComponentNode = toAst(`import { View, ${tabBarComponentName}, ${tabBarContainerComponentName}, ${tabBarPanelComponentName}} from '${PACKAGES['@tarojs/components']}'`)
const lastImportIndex = _.findLastIndex(astPath.node.body, t.isImportDeclaration)
const lastImportNode = astPath.get(`body.${lastImportIndex > -1 ? lastImportIndex : 0}`)
const extraNodes = [
importTaroH5Node,
importRouterNode,
initPxTransformNode
]
astPath.traverse(programExitVisitor)
if (hasJSX && !hasAddNervJsImportDefaultName) {
extraNodes.unshift(importNervjsNode)
}
if (tabBar) {
extraNodes.unshift(importComponentNode)
}
lastImportNode.insertAfter(extraNodes)
if (renderCallCode) {
const renderCallNode = toAst(renderCallCode)
astPath.pushContainer('body', renderCallNode)
}
}
}
})
const generateCode = generate(ast, {
jsescOption: {
minimal: true
}
}).code
return {
code: generateCode,
ast
}
}
function processOthers (code, filePath, fileType) {
let ast = wxTransformer({
code,
sourcePath: filePath,
isNormal: true,
isTyped: Util.REG_TYPESCRIPT.test(filePath),
adapter: 'h5'
}).ast
let taroImportDefaultName
let hasAddNervJsImportDefaultName = false
let hasJSX = false
let isPage = fileType === FILE_TYPE.PAGE
let hasComponentDidMount = false
let hasComponentDidShow = false
ast = babel.transformFromAst(ast, '', {
plugins: [
[require('babel-plugin-danger-remove-unused-import'), { ignore: ['@tarojs/taro', 'react', 'nervjs'] }]
]
}).ast
const ClassDeclarationOrExpression = {
enter (astPath) {
const node = astPath.node
if (!node.superClass) return
if (
node.superClass.type === 'MemberExpression' &&
node.superClass.object.name === taroImportDefaultName
) {
node.superClass.object.name = taroImportDefaultName
if (node.id === null) {
const renameComponentClassName = '_TaroComponentClass'
astPath.replaceWith(
t.classExpression(
t.identifier(renameComponentClassName),
node.superClass,
node.body,
node.decorators || []
)
)
}
} else if (node.superClass.name === 'Component') {
resetTSClassProperty(node.body.body)
if (node.id === null) {
const renameComponentClassName = '_TaroComponentClass'
astPath.replaceWith(
t.classExpression(
t.identifier(renameComponentClassName),
node.superClass,
node.body,
node.decorators || []
)
)
}
}
}
}
const programExitVisitor = {
ClassBody: {
exit (astPath) {
if (!hasComponentDidMount) {
astPath.pushContainer('body', t.classMethod(
'method', t.identifier('componentDidMount'), [],
t.blockStatement([]), false, false))
}
if (!hasComponentDidShow) {
astPath.pushContainer('body', t.classMethod(
'method', t.identifier('componentDidShow'), [],
t.blockStatement([]), false, false))
}
}
}
}
traverse(ast, {
ClassExpression: ClassDeclarationOrExpression,
ClassDeclaration: ClassDeclarationOrExpression,
ClassMethod: isPage ? {
exit (astPath) {
const node = astPath.node
const key = node.key
const keyName = getObjKey(key)
if (keyName === 'componentDidMount') {
hasComponentDidMount = true
} else if (keyName === 'componentDidShow') {
hasComponentDidShow = true
}
}
} : {},
ImportDeclaration: {
enter (astPath) {
const node = astPath.node
const source = node.source
let value = source.value
const specifiers = node.specifiers
if (Util.isAliasPath(value, pathAlias)) {
source.value = value = Util.replaceAliasPath(filePath, value, pathAlias)
}
if (!Util.isNpmPkg(value)) {
if (Util.REG_SCRIPTS.test(value)) {
const realPath = path.resolve(filePath, '..', value)
const dirname = path.dirname(realPath)
const extname = path.extname(realPath)
const removeExtPath = path.join(dirname, path.basename(realPath, extname))
node.source = t.stringLiteral(Util.promoteRelativePath(path.relative(filePath, removeExtPath)).replace(/\\/g, '/'))
}
} else if (value === PACKAGES['@tarojs/taro']) {
let specifier = specifiers.find(item => item.type === 'ImportDefaultSpecifier')
if (specifier) {
hasAddNervJsImportDefaultName = true
taroImportDefaultName = specifier.local.name
specifier.local.name = nervJsImportDefaultName
} else if (!hasAddNervJsImportDefaultName) {
hasAddNervJsImportDefaultName = true
node.specifiers.unshift(
t.importDefaultSpecifier(t.identifier(nervJsImportDefaultName))
)
}
const taroApisSpecifiers = []
const deletedIdx = []
specifiers.forEach((item, index) => {
if (item.imported && taroApis.indexOf(item.imported.name) >= 0) {
taroApisSpecifiers.push(t.importSpecifier(t.identifier(item.local.name), t.identifier(item.imported.name)))
deletedIdx.push(index)
}
})
_.pullAt(specifiers, deletedIdx)
source.value = PACKAGES['nervjs']
if (taroApisSpecifiers.length) {
astPath.insertBefore(t.importDeclaration(taroApisSpecifiers, t.stringLiteral(PACKAGES['@tarojs/taro-h5'])))
}
if (!specifiers.length) {
astPath.remove()
}
} else if (value === PACKAGES['@tarojs/redux']) {
source.value = PACKAGES['@tarojs/redux-h5']
} else if (value === PACKAGES['@tarojs/mobx']) {
source.value = PACKAGES['@tarojs/mobx-h5']
}
}
},
JSXElement: {
enter (astPath) {
hasJSX = true
}
},
Program: {
exit (astPath) {
if (isPage) {
astPath.traverse(programExitVisitor)
}
const node = astPath.node
if (hasJSX && !hasAddNervJsImportDefaultName) {
node.body.unshift(
t.importDeclaration([
t.importDefaultSpecifier(t.identifier(nervJsImportDefaultName))
], t.stringLiteral(PACKAGES['nervjs']))
)
}
if (taroImportDefaultName) {
const importTaro = toAst(`import ${taroImportDefaultName} from '${PACKAGES['@tarojs/taro-h5']}'`)
node.body.unshift(importTaro)
}
}
}
})
const generateCode = generate(ast, {
jsescOption: {
minimal: true
}
}).code
return {
code: generateCode,
ast
}
}
/**
* TS 编译器会把 class property 移到构造器
* 而小程序要求 `config` 和所有函数在初始化(after new Class)之后就收集到所有的函数和 config 信息
* 所以当如构造器里有 this.func = () => {...} 的形式就给他转换成普通的 classProperty function
* 如果有 config 就给他还原
*/
function resetTSClassProperty (body) {
for (const method of body) {
if (t.isClassMethod(method) && method.kind === 'constructor') {
for (const statement of _.cloneDeep(method.body.body)) {
if (t.isExpressionStatement(statement) && t.isAssignmentExpression(statement.expression)) {
const expr = statement.expression
const { left, right } = expr
if (
t.isMemberExpression(left) &&
t.isThisExpression(left.object) &&
t.isIdentifier(left.property)
) {
if (
(t.isArrowFunctionExpression(right) || t.isFunctionExpression(right)) ||
(left.property.name === 'config' && t.isObjectExpression(right))
) {
body.push(
t.classProperty(left.property, right)
)
_.remove(method.body.body, statement)
}
}
}
}
}
}
}
function classifyFiles (filename) {
const relPath = path.normalize(
path.relative(appPath, filename)
)
if (path.relative(filename, entryFilePath) === '') return FILE_TYPE.ENTRY
let relSrcPath = path.relative('src', relPath)
relSrcPath = path.format({
dir: path.dirname(relSrcPath),
base: path.basename(relSrcPath, path.extname(relSrcPath))
})
const isPage = pages.some(page => {
const relPage = path.normalize(
path.relative(appPath, page)
)
if (path.relative(relPage, relSrcPath) === '') return true
})
if (isPage) {
return FILE_TYPE.PAGE
} else {
return FILE_TYPE.NORMAL
}
}
function getDist (filename, isScriptFile) {
const dirname = path.dirname(filename)
const distDirname = dirname.replace(sourcePath, tempDir)
return isScriptFile
? path.format({
dir: distDirname,
ext: '.js',
name: path.basename(filename, path.extname(filename))
})
: path.format({
dir: distDirname,
base: path.basename(filename)
})
}
function processFiles (filePath) {
const file = fs.readFileSync(filePath)
const dirname = path.dirname(filePath)
const extname = path.extname(filePath)
const distDirname = dirname.replace(sourcePath, tempDir)
const isScriptFile = Util.REG_SCRIPTS.test(extname)
const distPath = getDist(filePath, isScriptFile)
try {
if (isScriptFile) {
// 脚本文件 处理一下
const fileType = classifyFiles(filePath)
const content = file.toString()
const transformResult = fileType === FILE_TYPE.ENTRY
? processEntry(content, filePath)
: processOthers(content, filePath, fileType)
const jsCode = transformResult.code
fs.ensureDirSync(distDirname)
fs.writeFileSync(distPath, Buffer.from(jsCode))
} else {
// 其他 直接复制
fs.ensureDirSync(distDirname)
fs.copySync(filePath, distPath)
}
} catch (e) {
console.log(e)
}
}
function watchFiles () {
const watcher = chokidar.watch(path.join(sourcePath), {
ignored: /(^|[/\\])\../,
persistent: true,
ignoreInitial: true
})
watcher
.on('add', filePath => {
pages = []
const relativePath = path.relative(appPath, filePath)
Util.printLog(Util.pocessTypeEnum.CREATE, '添加文件', relativePath)
processFiles(filePath)
})
.on('change', filePath => {
pages = []
const relativePath = path.relative(appPath, filePath)
Util.printLog(Util.pocessTypeEnum.MODIFY, '文件变动', relativePath)
processFiles(filePath)
})
.on('unlink', filePath => {
const relativePath = path.relative(appPath, filePath)
const extname = path.extname(relativePath)
const isScriptFile = Util.REG_SCRIPTS.test(extname)
const dist = getDist(filePath, isScriptFile)
Util.printLog(Util.pocessTypeEnum.UNLINK, '删除文件', relativePath)
fs.unlinkSync(dist)
})
}
function buildTemp () {
fs.ensureDirSync(tempPath)
return new Promise((resolve, reject) => {
klaw(sourcePath)
.on('data', file => {
const relativePath = path.relative(appPath, file.path)
if (!file.stats.isDirectory()) {
Util.printLog(Util.pocessTypeEnum.CREATE, '发现文件', relativePath)
processFiles(file.path)
}
})
.on('end', () => {
resolve()
})
})
}
async function buildDist (buildConfig) {
const { watch } = buildConfig
const entryFile = path.basename(entryFileName, path.extname(entryFileName)) + '.js'
const sourceRoot = projectConfig.sourceRoot || CONFIG.SOURCE_DIR
h5Config.env = projectConfig.env
Object.assign(h5Config.env, {
TARO_ENV: JSON.stringify(Util.BUILD_TYPES.H5)
})
h5Config.defineConstants = projectConfig.defineConstants
h5Config.plugins = projectConfig.plugins
h5Config.designWidth = projectConfig.designWidth
if (projectConfig.deviceRatio) {
h5Config.deviceRatio = projectConfig.deviceRatio
}
h5Config.sourceRoot = sourceRoot
h5Config.outputRoot = outputDir
h5Config.entry = Object.assign({
app: [path.join(tempPath, entryFile)]
}, h5Config.entry)
if (watch) {
h5Config.isWatch = true
}
const webpackRunner = await npmProcess.getNpmPkg('@tarojs/webpack-runner')
webpackRunner(h5Config)
}
const pRimraf = promisify(rimraf)
async function clean () {
try {
await pRimraf(tempPath)
await pRimraf(outputPath)
} catch (e) {
console.log(e)
}
}
function copyFileSync (from, to, options) {
const filename = path.basename(from)
if (fs.statSync(from).isFile() && !path.extname(to)) {
fs.ensureDir(to)
return fs.copySync(from, path.join(to, filename), options)
}
fs.ensureDir(path.dirname(to))
return fs.copySync(from, to, options)
}
function copyFiles () {
const copyConfig = projectConfig.copy || { patterns: [], options: {} }
if (copyConfig.patterns && copyConfig.patterns.length) {
copyConfig.options = copyConfig.options || {}
const globalIgnore = copyConfig.options.ignore
const projectDir = appPath
copyConfig.patterns.forEach(pattern => {
if (typeof pattern === 'object' && pattern.from && pattern.to) {
const from = path.join(projectDir, pattern.from)
const to = path.join(projectDir, pattern.to)
let ignore = pattern.ignore || globalIgnore
if (fs.existsSync(from)) {
const copyOptions = {}
if (ignore) {
ignore = Array.isArray(ignore) ? ignore : [ignore]
copyOptions.filter = src => {
let isMatch = false
ignore.forEach(iPa => {
if (minimatch(path.basename(src), iPa)) {
isMatch = true
}
})
return !isMatch
}
}
copyFileSync(from, to, copyOptions)
} else {
Util.printLog(Util.pocessTypeEnum.ERROR, '拷贝失败', `${pattern.from} 文件不存在!`)
}
}
})
}
}
async function build (buildConfig) {
process.env.TARO_ENV = Util.BUILD_TYPES.H5
await clean()
copyFiles()
await buildTemp(buildConfig)
await buildDist(buildConfig)
if (buildConfig.watch) {
watchFiles()
}
}
module.exports = {
build,
buildTemp,
processFiles
}

View File

@ -0,0 +1,173 @@
const path = require('path')
const fs = require('fs-extra')
const chalk = require('chalk')
const inquirer = require('inquirer')
const semver = require('semver')
const Creator = require('./creator')
const {
shouldUseYarn,
shouldUseCnpm,
getPkgVersion
} = require('./util')
const { SOURCE_DIR } = require('./config')
class Project extends Creator {
constructor (options) {
super()
const unSupportedVer = semver.lt(process.version, 'v7.6.0')
if (unSupportedVer) {
throw new Error('Node.js 版本过低,推荐升级 Node.js 至 v8.0.0+')
}
this.rootPath = this._rootPath
this.conf = Object.assign({
projectName: null,
template: null,
description: ''
}, options)
}
init () {
console.log(chalk.green(`Taro即将创建一个新项目!`))
console.log('Need help? Go and open issue: https://github.com/NervJS/taro/issues/new')
console.log()
}
create () {
this.ask()
.then(answers => {
const date = new Date()
this.conf = Object.assign(this.conf, answers)
this.conf.date = `${date.getFullYear()}-${(date.getMonth() + 1)}-${date.getDate()}`
this.write()
})
}
ask () {
const prompts = []
const conf = this.conf
if (typeof conf.projectName !== 'string') {
prompts.push({
type: 'input',
name: 'projectName',
message: '请输入项目名称!',
validate (input) {
if (!input) {
return '项目名不能为空!'
}
if (fs.existsSync(input)) {
return '当前目录已经存在同名项目,请换一个项目名!'
}
return true
}
})
} else if (fs.existsSync(conf.projectName)) {
prompts.push({
type: 'input',
name: 'projectName',
message: '当前目录已经存在同名项目,请换一个项目名!',
validate (input) {
if (!input) {
return '项目名不能为空!'
}
if (fs.existsSync(input)) {
return '项目名依然重复!'
}
return true
}
})
}
if (typeof conf.description !== 'string') {
prompts.push({
type: 'input',
name: 'description',
message: '请输入项目介绍!'
})
}
if (typeof conf.typescript !== 'boolean') {
prompts.push({
type: 'confirm',
name: 'typescript',
message: '是否需要使用 TypeScript '
})
}
const cssChoices = [{
name: 'Sass',
value: 'sass'
}, {
name: 'Less',
value: 'less'
}, {
name: 'Stylus',
value: 'stylus'
}, {
name: '无',
value: 'none'
}]
if (typeof conf.css !== 'string') {
prompts.push({
type: 'list',
name: 'css',
message: '请选择 CSS 预处理器Sass/Less/Stylus',
choices: cssChoices
})
}
const templateChoices = [{
name: '默认模板',
value: 'default'
}, {
name: 'Redux 模板',
value: 'redux'
}, {
name: 'Mobx 模板',
value: 'mobx'
}]
if (typeof conf.template !== 'string') {
prompts.push({
type: 'list',
name: 'template',
message: '请选择模板',
choices: templateChoices
})
} else {
let isTemplateExist = false
templateChoices.forEach(item => {
if (item.value === conf.template) {
isTemplateExist = true
}
})
if (!isTemplateExist) {
console.log(chalk.red('你选择的模板不存在!'))
console.log(chalk.red('目前提供了以下模板以供使用:'))
console.log()
templateChoices.forEach(item => {
console.log(chalk.green(`- ${item.name}`))
})
process.exit(1)
}
}
return inquirer.prompt(prompts)
}
write (cb) {
const { template } = this.conf
this.conf.src = SOURCE_DIR
const templateCreate = require(path.join(this.templatePath(), template, 'index.js'))
templateCreate(this, this.conf, {
shouldUseYarn,
shouldUseCnpm,
getPkgVersion
}, cb)
}
}
module.exports = Project

View File

@ -0,0 +1,249 @@
const fs = require('fs-extra')
const path = require('path')
const {performance} = require('perf_hooks')
const chokidar = require('chokidar')
const chalk = require('chalk')
const ejs = require('ejs')
const _ = require('lodash')
const shelljs = require('shelljs')
const klaw = require('klaw')
const Util = require('./util')
const npmProcess = require('./util/npm')
const CONFIG = require('./config')
const {getPkgVersion} = require('./util')
const StyleProcess = require('./rn/styleProcess')
const {transformJSCode} = require('./rn/transformJS')
const appPath = process.cwd()
const projectConfig = require(path.join(appPath, Util.PROJECT_CONFIG))(_.merge)
const sourceDirName = projectConfig.sourceRoot || CONFIG.SOURCE_DIR
const sourceDir = path.join(appPath, sourceDirName)
const tempDir = '.rn_temp'
const tempPath = path.join(appPath, tempDir)
const entryFilePath = Util.resolveScriptPath(path.join(sourceDir, CONFIG.ENTRY))
const entryFileName = path.basename(entryFilePath)
const pluginsConfig = projectConfig.plugins || {}
const pkgPath = path.join(__dirname, './rn/pkg')
let depTree = {}
let isBuildingStyles = {}
const styleDenpendencyTree = {}
function isEntryFile (filePath) {
return path.basename(filePath) === entryFileName
}
function compileDepStyles (filePath, styleFiles) {
if (isBuildingStyles[filePath] || styleFiles.length === 0) {
return Promise.resolve({})
}
isBuildingStyles[filePath] = true
return Promise.all(styleFiles.map(async p => { // to css string
const filePath = path.join(p)
const fileExt = path.extname(filePath)
Util.printLog(Util.pocessTypeEnum.COMPILE, _.camelCase(fileExt).toUpperCase(), filePath)
return StyleProcess.loadStyle({filePath, pluginsConfig})
})).then(resList => { // postcss
return Promise.all(resList.map(item => {
return StyleProcess.postCSS({...item, projectConfig})
}))
}).then(resList => {
let styleObjectEntire = {}
resList.forEach(item => {
let styleObject = StyleProcess.getStyleObject({css: item.css, filePath: item.filePath})
// validate styleObject
StyleProcess.validateStyle({styleObject, filePath: item.filePath})
Object.assign(styleObjectEntire, styleObject)
if (filePath !== entryFilePath) { // 非入口文件,合并全局样式
Object.assign(styleObjectEntire, _.get(styleDenpendencyTree, [entryFilePath, 'styleObjectEntire'], {}))
}
styleDenpendencyTree[filePath] = {
styleFiles,
styleObjectEntire
}
})
return JSON.stringify(styleObjectEntire, null, 2)
}).then(css => {
let tempFilePath = filePath.replace(sourceDir, tempPath)
const basename = path.basename(tempFilePath, path.extname(tempFilePath))
tempFilePath = path.join(path.dirname(tempFilePath), `${basename}_styles.js`)
StyleProcess.writeStyleFile({css, tempFilePath})
}).catch((e) => {
throw new Error(e)
})
}
function initProjectFile () {
// generator app.json
const appJsonObject = Object.assign({}, {
expo: {
sdkVersion: '27.0.0'
}
}, projectConfig.rn && projectConfig.rn.appJson)
// generator .${tempPath}/package.json TODO JSON.parse 这种写法可能会有隐患
const pkgTempObj = JSON.parse(
ejs.render(
fs.readFileSync(pkgPath, 'utf-8'), {
projectName: projectConfig.projectName,
version: getPkgVersion()
}
).replace(/(\r\n|\n|\r|\s+)/gm, '')
)
const dependencies = require(path.join(process.cwd(), 'package.json')).dependencies
pkgTempObj.dependencies = Object.assign({}, pkgTempObj.dependencies, dependencies)
// Copy bin/crna-entry.js ?
const crnaEntryPath = path.join(path.dirname(npmProcess.resolveNpmSync('@tarojs/rn-runner')), 'src/bin/crna-entry.js')
fs.writeFileSync(path.join(tempDir, 'app.json'), JSON.stringify(appJsonObject, null, 2))
Util.printLog(Util.pocessTypeEnum.GENERATE, 'app.json', path.join(tempPath, 'app.json'))
fs.writeFileSync(path.join(tempDir, 'package.json'), JSON.stringify(pkgTempObj, null, 2))
Util.printLog(Util.pocessTypeEnum.GENERATE, 'package.json', path.join(tempPath, 'package.json'))
fs.copySync(crnaEntryPath, path.join(tempDir, 'bin/crna-entry.js'))
Util.printLog(Util.pocessTypeEnum.COPY, 'crna-entry.js', path.join(tempPath, 'bin/crna-entry.js'))
}
async function processFile (filePath) {
if (!fs.existsSync(filePath)) {
return
}
const dirname = path.dirname(filePath)
const distDirname = dirname.replace(sourceDir, tempDir)
let distPath = path.format({dir: distDirname, base: path.basename(filePath)})
const code = fs.readFileSync(filePath, 'utf-8')
if (Util.REG_STYLE.test(filePath)) {
// do something
} else if (Util.REG_SCRIPTS.test(filePath)) {
if (Util.REG_TYPESCRIPT.test(filePath)) {
distPath = distPath.replace(/\.(tsx|ts)(\?.*)?$/, '.js')
}
Util.printLog(Util.pocessTypeEnum.COMPILE, _.camelCase(path.extname(filePath)).toUpperCase(), filePath)
// transformJSCode
let transformResult = transformJSCode({code, filePath, isEntryFile: isEntryFile(filePath), projectConfig})
const jsCode = transformResult.code
fs.ensureDirSync(distDirname)
fs.writeFileSync(distPath, Buffer.from(jsCode))
// compileDepStyles
const styleFiles = transformResult.styleFiles
depTree[filePath] = styleFiles
await compileDepStyles(filePath, styleFiles)
} else {
fs.ensureDirSync(distDirname)
fs.copySync(filePath, distPath)
Util.printLog(Util.pocessTypeEnum.COPY, _.camelCase(path.extname(filePath)).toUpperCase(), filePath)
}
}
function buildTemp () {
fs.ensureDirSync(path.join(tempPath, 'bin'))
return new Promise((resolve, reject) => {
klaw(sourceDir)
.on('data', file => {
if (!file.stats.isDirectory()) {
processFile(file.path)
}
})
.on('end', () => {
initProjectFile()
if (!fs.existsSync(path.join(tempPath, 'node_modules'))) {
console.log()
console.log(chalk.yellow('开始安装依赖~'))
process.chdir(tempPath)
let command
if (Util.shouldUseYarn()) {
command = 'yarn'
} else if (Util.shouldUseCnpm()) {
command = 'cnpm install'
} else {
command = 'npm install'
}
shelljs.exec(command, {silent: false})
}
resolve()
})
})
}
async function buildDist ({watch}) {
const entry = {
app: path.join(tempPath, entryFileName)
}
const rnConfig = projectConfig.rn || {}
rnConfig.env = projectConfig.env
rnConfig.defineConstants = projectConfig.defineConstants
rnConfig.designWidth = projectConfig.designWidth
rnConfig.entry = entry
if (watch) {
rnConfig.isWatch = true
}
rnConfig.projectDir = tempPath
const rnRunner = await npmProcess.getNpmPkg('@tarojs/rn-runner')
rnRunner(rnConfig)
}
async function perfWrap (callback, args) {
isBuildingStyles = {} // 清空
// 后期可以优化,不编译全部
let t0 = performance.now()
await callback(args)
let t1 = performance.now()
Util.printLog(Util.pocessTypeEnum.COMPILE, `编译完成,花费${Math.round(t1 - t0)} ms`)
}
function watchFiles () {
const watcher = chokidar.watch(path.join(sourceDir), {
ignored: /(^|[/\\])\../,
persistent: true,
ignoreInitial: true
})
watcher
.on('ready', () => {
console.log()
console.log(chalk.gray('初始化完毕,监听文件修改中...'))
console.log()
})
.on('add', filePath => {
const relativePath = path.relative(appPath, filePath)
Util.printLog(Util.pocessTypeEnum.CREATE, '添加文件', relativePath)
perfWrap(buildTemp)
})
.on('change', filePath => {
const relativePath = path.relative(appPath, filePath)
Util.printLog(Util.pocessTypeEnum.MODIFY, '文件变动', relativePath)
if (Util.REG_SCRIPTS.test(filePath)) {
perfWrap(processFile, filePath)
}
if (Util.REG_STYLE.test(filePath)) {
_.forIn(depTree, (styleFiles, jsFilePath) => {
if (styleFiles.indexOf(filePath) > -1) {
perfWrap(processFile, jsFilePath)
}
})
}
})
.on('unlink', filePath => {
const relativePath = path.relative(appPath, filePath)
Util.printLog(Util.pocessTypeEnum.UNLINK, '删除文件', relativePath)
perfWrap(buildTemp)
})
.on('error', error => console.log(`Watcher error: ${error}`))
}
async function build ({watch}) {
fs.ensureDirSync(tempPath)
let t0 = performance.now()
await buildTemp()
let t1 = performance.now()
Util.printLog(Util.pocessTypeEnum.COMPILE, `编译完成,花费${Math.round(t1 - t0)} ms`)
await buildDist({watch})
if (watch) {
watchFiles()
}
}
module.exports = {build}

View File

@ -0,0 +1,76 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
'use strict'
const normalizeColor = require('./normalizeColor')
const colorPropType = function (
isRequired,
props,
propName,
componentName,
location,
propFullName
) {
const color = props[propName]
if (color === undefined || color === null) {
if (isRequired) {
return new Error(
'Required ' +
location +
' `' +
(propFullName || propName) +
'` was not specified in `' +
componentName +
'`.'
)
}
return
}
if (typeof color === 'number') {
// Developers should not use a number, but we are using the prop type
// both for user provided colors and for transformed ones. This isn't ideal
// and should be fixed but will do for now...
return
}
if (normalizeColor(color) === null) {
return new Error(
'Invalid ' +
location +
' `' +
(propFullName || propName) +
'` supplied to `' +
componentName +
'`: ' +
color +
'\n' +
`Valid color formats are
- '#f0f' (#rgb)
- '#f0fc' (#rgba)
- '#ff00ff' (#rrggbb)
- '#ff00ff00' (#rrggbbaa)
- 'rgb(255, 255, 255)'
- 'rgba(255, 255, 255, 1.0)'
- 'hsl(360, 100%, 100%)'
- 'hsla(360, 100%, 100%, 1.0)'
- 'transparent'
- 'red'
- 0xff00ff00 (0xrrggbbaa)
`
)
}
}
const ColorPropType = colorPropType.bind(null, false /* isRequired */)
ColorPropType.isRequired = colorPropType.bind(null, true /* isRequired */)
module.exports = ColorPropType

View File

@ -0,0 +1,52 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict'
/* $FlowFixMe(>=0.54.0 site=react_native_oss) This comment suppresses an error
* found when Flow v0.54 was deployed. To see the error delete this comment and
* run Flow. */
const keyMirror = require('fbjs/lib/keyMirror')
/**
* ImageResizeMode - Enum for different image resizing modes, set via
* `resizeMode` style property on `<Image>` components.
*/
const ImageResizeMode = keyMirror({
/**
* contain - The image will be resized such that it will be completely
* visible, contained within the frame of the View.
*/
contain: null,
/**
* cover - The image will be resized such that the entire area of the view
* is covered by the image, potentially clipping parts of the image.
*/
cover: null,
/**
* stretch - The image will be stretched to fill the entire frame of the
* view without clipping. This may change the aspect ratio of the image,
* distorting it.
*/
stretch: null,
/**
* center - The image will be scaled down such that it is completely visible,
* if bigger than the area of the view.
* The image will not be scaled up.
*/
center: null,
/**
* repeat - The image will be repeated to cover the frame of the View. The
* image will keep it's size and aspect ratio.
*/
repeat: null
})
module.exports = ImageResizeMode

View File

@ -0,0 +1,62 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict'
const ColorPropType = require('./ColorPropType')
const ImageResizeMode = require('./ImageResizeMode')
const LayoutPropTypes = require('./LayoutPropTypes')
const ReactPropTypes = require('prop-types')
const ShadowPropTypesIOS = require('./ShadowPropTypesIOS')
const TransformPropTypes = require('./TransformPropTypes')
const ImageStylePropTypes = {
...LayoutPropTypes,
...ShadowPropTypesIOS,
...TransformPropTypes,
resizeMode: ReactPropTypes.oneOf(Object.keys(ImageResizeMode)),
backfaceVisibility: ReactPropTypes.oneOf(['visible', 'hidden']),
backgroundColor: ColorPropType,
borderColor: ColorPropType,
borderWidth: ReactPropTypes.number,
borderRadius: ReactPropTypes.number,
overflow: ReactPropTypes.oneOf(['visible', 'hidden']),
/**
* Changes the color of all the non-transparent pixels to the tintColor.
*/
tintColor: ColorPropType,
opacity: ReactPropTypes.number,
/**
* When the image has rounded corners, specifying an overlayColor will
* cause the remaining space in the corners to be filled with a solid color.
* This is useful in cases which are not supported by the Android
* implementation of rounded corners:
* - Certain resize modes, such as 'contain'
* - Animated GIFs
*
* A typical way to use this prop is with images displayed on a solid
* background and setting the `overlayColor` to the same color
* as the background.
*
* For details of how this works under the hood, see
* http://frescolib.org/docs/rounded-corners-and-circles.html
*
* @platform android
*/
overlayColor: ReactPropTypes.string,
// Android-Specific styles
borderTopLeftRadius: ReactPropTypes.number,
borderTopRightRadius: ReactPropTypes.number,
borderBottomLeftRadius: ReactPropTypes.number,
borderBottomRightRadius: ReactPropTypes.number
}
module.exports = ImageStylePropTypes

View File

@ -0,0 +1,561 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* strict
*/
'use strict'
const ReactPropTypes = require('prop-types')
/**
* React Native's layout system is based on Flexbox and is powered both
* on iOS and Android by an open source project called `Yoga`:
* https://github.com/facebook/yoga
*
* The implementation in Yoga is slightly different from what the
* Flexbox spec defines - for example, we chose more sensible default
* values. Since our layout docs are generated from the comments in this
* file, please keep a brief comment describing each prop type.
*
* These properties are a subset of our styles that are consumed by the layout
* algorithm and affect the positioning and sizing of views.
*/
const LayoutPropTypes = {
/** `display` sets the display type of this component.
*
* It works similarly to `display` in CSS, but only support 'flex' and 'none'.
* 'flex' is the default.
*/
display: ReactPropTypes.oneOf(['none', 'flex']),
/** `width` sets the width of this component.
*
* It works similarly to `width` in CSS, but in React Native you
* must use points or percentages. Ems and other units are not supported.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/width for more details.
*/
width: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/** `height` sets the height of this component.
*
* It works similarly to `height` in CSS, but in React Native you
* must use points or percentages. Ems and other units are not supported.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/height for more details.
*/
height: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/**
* When the direction is `ltr`, `start` is equivalent to `left`.
* When the direction is `rtl`, `start` is equivalent to `right`.
*
* This style takes precedence over the `left`, `right`, and `end` styles.
*/
start: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/**
* When the direction is `ltr`, `end` is equivalent to `right`.
* When the direction is `rtl`, `end` is equivalent to `left`.
*
* This style takes precedence over the `left` and `right` styles.
*/
end: ReactPropTypes.oneOfType([ReactPropTypes.number, ReactPropTypes.string]),
/** `top` is the number of logical pixels to offset the top edge of
* this component.
*
* It works similarly to `top` in CSS, but in React Native you
* must use points or percentages. Ems and other units are not supported.
*
* See https://developer.mozilla.org/en-US/docs/Web/CSS/top
* for more details of how `top` affects layout.
*/
top: ReactPropTypes.oneOfType([ReactPropTypes.number, ReactPropTypes.string]),
/** `left` is the number of logical pixels to offset the left edge of
* this component.
*
* It works similarly to `left` in CSS, but in React Native you
* must use points or percentages. Ems and other units are not supported.
*
* See https://developer.mozilla.org/en-US/docs/Web/CSS/left
* for more details of how `left` affects layout.
*/
left: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/** `right` is the number of logical pixels to offset the right edge of
* this component.
*
* It works similarly to `right` in CSS, but in React Native you
* must use points or percentages. Ems and other units are not supported.
*
* See https://developer.mozilla.org/en-US/docs/Web/CSS/right
* for more details of how `right` affects layout.
*/
right: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/** `bottom` is the number of logical pixels to offset the bottom edge of
* this component.
*
* It works similarly to `bottom` in CSS, but in React Native you
* must use points or percentages. Ems and other units are not supported.
*
* See https://developer.mozilla.org/en-US/docs/Web/CSS/bottom
* for more details of how `bottom` affects layout.
*/
bottom: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/** `minWidth` is the minimum width for this component, in logical pixels.
*
* It works similarly to `min-width` in CSS, but in React Native you
* must use points or percentages. Ems and other units are not supported.
*
* See https://developer.mozilla.org/en-US/docs/Web/CSS/min-width
* for more details.
*/
minWidth: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/** `maxWidth` is the maximum width for this component, in logical pixels.
*
* It works similarly to `max-width` in CSS, but in React Native you
* must use points or percentages. Ems and other units are not supported.
*
* See https://developer.mozilla.org/en-US/docs/Web/CSS/max-width
* for more details.
*/
maxWidth: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/** `minHeight` is the minimum height for this component, in logical pixels.
*
* It works similarly to `min-height` in CSS, but in React Native you
* must use points or percentages. Ems and other units are not supported.
*
* See https://developer.mozilla.org/en-US/docs/Web/CSS/min-height
* for more details.
*/
minHeight: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/** `maxHeight` is the maximum height for this component, in logical pixels.
*
* It works similarly to `max-height` in CSS, but in React Native you
* must use points or percentages. Ems and other units are not supported.
*
* See https://developer.mozilla.org/en-US/docs/Web/CSS/max-height
* for more details.
*/
maxHeight: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/** Setting `margin` has the same effect as setting each of
* `marginTop`, `marginLeft`, `marginBottom`, and `marginRight`.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/margin
* for more details.
*/
margin: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/** Setting `marginVertical` has the same effect as setting both
* `marginTop` and `marginBottom`.
*/
marginVertical: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/** Setting `marginHorizontal` has the same effect as setting
* both `marginLeft` and `marginRight`.
*/
marginHorizontal: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/** `marginTop` works like `margin-top` in CSS.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/margin-top
* for more details.
*/
marginTop: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/** `marginBottom` works like `margin-bottom` in CSS.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/margin-bottom
* for more details.
*/
marginBottom: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/** `marginLeft` works like `margin-left` in CSS.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/margin-left
* for more details.
*/
marginLeft: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/** `marginRight` works like `margin-right` in CSS.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/margin-right
* for more details.
*/
marginRight: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/**
* When direction is `ltr`, `marginStart` is equivalent to `marginLeft`.
* When direction is `rtl`, `marginStart` is equivalent to `marginRight`.
*/
marginStart: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/**
* When direction is `ltr`, `marginEnd` is equivalent to `marginRight`.
* When direction is `rtl`, `marginEnd` is equivalent to `marginLeft`.
*/
marginEnd: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/** Setting `padding` has the same effect as setting each of
* `paddingTop`, `paddingBottom`, `paddingLeft`, and `paddingRight`.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/padding
* for more details.
*/
padding: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/** Setting `paddingVertical` is like setting both of
* `paddingTop` and `paddingBottom`.
*/
paddingVertical: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/** Setting `paddingHorizontal` is like setting both of
* `paddingLeft` and `paddingRight`.
*/
paddingHorizontal: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/** `paddingTop` works like `padding-top` in CSS.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/padding-top
* for more details.
*/
paddingTop: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/** `paddingBottom` works like `padding-bottom` in CSS.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/padding-bottom
* for more details.
*/
paddingBottom: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/** `paddingLeft` works like `padding-left` in CSS.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/padding-left
* for more details.
*/
paddingLeft: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/** `paddingRight` works like `padding-right` in CSS.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/padding-right
* for more details.
*/
paddingRight: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/**
* When direction is `ltr`, `paddingStart` is equivalent to `paddingLeft`.
* When direction is `rtl`, `paddingStart` is equivalent to `paddingRight`.
*/
paddingStart: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/**
* When direction is `ltr`, `paddingEnd` is equivalent to `paddingRight`.
* When direction is `rtl`, `paddingEnd` is equivalent to `paddingLeft`.
*/
paddingEnd: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/** `borderWidth` works like `border-width` in CSS.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/border-width
* for more details.
*/
borderWidth: ReactPropTypes.number,
/** `borderTopWidth` works like `border-top-width` in CSS.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/border-top-width
* for more details.
*/
borderTopWidth: ReactPropTypes.number,
/**
* When direction is `ltr`, `borderStartWidth` is equivalent to `borderLeftWidth`.
* When direction is `rtl`, `borderStartWidth` is equivalent to `borderRightWidth`.
*/
borderStartWidth: ReactPropTypes.number,
/**
* When direction is `ltr`, `borderEndWidth` is equivalent to `borderRightWidth`.
* When direction is `rtl`, `borderEndWidth` is equivalent to `borderLeftWidth`.
*/
borderEndWidth: ReactPropTypes.number,
/** `borderRightWidth` works like `border-right-width` in CSS.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/border-right-width
* for more details.
*/
borderRightWidth: ReactPropTypes.number,
/** `borderBottomWidth` works like `border-bottom-width` in CSS.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/border-bottom-width
* for more details.
*/
borderBottomWidth: ReactPropTypes.number,
/** `borderLeftWidth` works like `border-left-width` in CSS.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/border-left-width
* for more details.
*/
borderLeftWidth: ReactPropTypes.number,
/** `position` in React Native is similar to regular CSS, but
* everything is set to `relative` by default, so `absolute`
* positioning is always just relative to the parent.
*
* If you want to position a child using specific numbers of logical
* pixels relative to its parent, set the child to have `absolute`
* position.
*
* If you want to position a child relative to something
* that is not its parent, just don't use styles for that. Use the
* component tree.
*
* See https://github.com/facebook/yoga
* for more details on how `position` differs between React Native
* and CSS.
*/
position: ReactPropTypes.oneOf(['absolute', 'relative']),
/** `flexDirection` controls which directions children of a container go.
* `row` goes left to right, `column` goes top to bottom, and you may
* be able to guess what the other two do. It works like `flex-direction`
* in CSS, except the default is `column`.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/flex-direction
* for more details.
*/
flexDirection: ReactPropTypes.oneOf([
'row',
'row-reverse',
'column',
'column-reverse'
]),
/** `flexWrap` controls whether children can wrap around after they
* hit the end of a flex container.
* It works like `flex-wrap` in CSS (default: nowrap).
* See https://developer.mozilla.org/en-US/docs/Web/CSS/flex-wrap
* for more details.
*/
flexWrap: ReactPropTypes.oneOf(['wrap', 'nowrap', 'wrap-reverse']),
/** `justifyContent` aligns children in the main direction.
* For example, if children are flowing vertically, `justifyContent`
* controls how they align vertically.
* It works like `justify-content` in CSS (default: flex-start).
* See https://developer.mozilla.org/en-US/docs/Web/CSS/justify-content
* for more details.
*/
justifyContent: ReactPropTypes.oneOf([
'flex-start',
'flex-end',
'center',
'space-between',
'space-around',
'space-evenly'
]),
/** `alignItems` aligns children in the cross direction.
* For example, if children are flowing vertically, `alignItems`
* controls how they align horizontally.
* It works like `align-items` in CSS (default: stretch).
* See https://developer.mozilla.org/en-US/docs/Web/CSS/align-items
* for more details.
*/
alignItems: ReactPropTypes.oneOf([
'flex-start',
'flex-end',
'center',
'stretch',
'baseline'
]),
/** `alignSelf` controls how a child aligns in the cross direction,
* overriding the `alignItems` of the parent. It works like `align-self`
* in CSS (default: auto).
* See https://developer.mozilla.org/en-US/docs/Web/CSS/align-self
* for more details.
*/
alignSelf: ReactPropTypes.oneOf([
'auto',
'flex-start',
'flex-end',
'center',
'stretch',
'baseline'
]),
/** `alignContent` controls how rows align in the cross direction,
* overriding the `alignContent` of the parent.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/align-content
* for more details.
*/
alignContent: ReactPropTypes.oneOf([
'flex-start',
'flex-end',
'center',
'stretch',
'space-between',
'space-around'
]),
/** `overflow` controls how children are measured and displayed.
* `overflow: hidden` causes views to be clipped while `overflow: scroll`
* causes views to be measured independently of their parents main axis.
* It works like `overflow` in CSS (default: visible).
* See https://developer.mozilla.org/en/docs/Web/CSS/overflow
* for more details.
* `overflow: visible` only works on iOS. On Android, all views will clip
* their children.
*/
overflow: ReactPropTypes.oneOf(['visible', 'hidden', 'scroll']),
/** In React Native `flex` does not work the same way that it does in CSS.
* `flex` is a number rather than a string, and it works
* according to the `Yoga` library
* at https://github.com/facebook/yoga
*
* When `flex` is a positive number, it makes the component flexible
* and it will be sized proportional to its flex value. So a
* component with `flex` set to 2 will take twice the space as a
* component with `flex` set to 1.
*
* When `flex` is 0, the component is sized according to `width`
* and `height` and it is inflexible.
*
* When `flex` is -1, the component is normally sized according
* `width` and `height`. However, if there's not enough space,
* the component will shrink to its `minWidth` and `minHeight`.
*
* flexGrow, flexShrink, and flexBasis work the same as in CSS.
*/
flex: ReactPropTypes.number,
flexGrow: ReactPropTypes.number,
flexShrink: ReactPropTypes.number,
flexBasis: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/**
* Aspect ratio control the size of the undefined dimension of a node. Aspect ratio is a
* non-standard property only available in react native and not CSS.
*
* - On a node with a set width/height aspect ratio control the size of the unset dimension
* - On a node with a set flex basis aspect ratio controls the size of the node in the cross axis
* if unset
* - On a node with a measure function aspect ratio works as though the measure function measures
* the flex basis
* - On a node with flex grow/shrink aspect ratio controls the size of the node in the cross axis
* if unset
* - Aspect ratio takes min/max dimensions into account
*/
aspectRatio: ReactPropTypes.number,
/** `zIndex` controls which components display on top of others.
* Normally, you don't use `zIndex`. Components render according to
* their order in the document tree, so later components draw over
* earlier ones. `zIndex` may be useful if you have animations or custom
* modal interfaces where you don't want this behavior.
*
* It works like the CSS `z-index` property - components with a larger
* `zIndex` will render on top. Think of the z-direction like it's
* pointing from the phone into your eyeball.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/z-index for
* more details.
*/
zIndex: ReactPropTypes.number,
/** `direction` specifies the directional flow of the user interface.
* The default is `inherit`, except for root node which will have
* value based on the current locale.
* See https://facebook.github.io/yoga/docs/rtl/
* for more details.
* @platform ios
*/
direction: ReactPropTypes.oneOf(['inherit', 'ltr', 'rtl'])
}
module.exports = LayoutPropTypes

View File

@ -0,0 +1,50 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict'
const ColorPropType = require('./ColorPropType')
const ReactPropTypes = require('prop-types')
/**
* These props can be used to dynamically generate shadows on views, images, text, etc.
*
* Because they are dynamically generated, they may cause performance regressions. Static
* shadow image asset may be a better way to go for optimal performance.
*
* These properties are iOS only - for similar functionality on Android, use the [`elevation`
* property](docs/viewstyleproptypes.html#elevation).
*/
const ShadowPropTypesIOS = {
/**
* Sets the drop shadow color
* @platform ios
*/
shadowColor: ColorPropType,
/**
* Sets the drop shadow offset
* @platform ios
*/
shadowOffset: ReactPropTypes.shape({
width: ReactPropTypes.number,
height: ReactPropTypes.number
}),
/**
* Sets the drop shadow opacity (multiplied by the color's alpha component)
* @platform ios
*/
shadowOpacity: ReactPropTypes.number,
/**
* Sets the drop shadow blur radius
* @platform ios
*/
shadowRadius: ReactPropTypes.number
}
module.exports = ShadowPropTypesIOS

View File

@ -0,0 +1,82 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*
*/
'use strict'
const ImageStylePropTypes = require('./ImageStylePropTypes')
const TextStylePropTypes = require('./TextStylePropTypes')
const ViewStylePropTypes = require('./ViewStylePropTypes')
const invariant = require('fbjs/lib/invariant')
// Hardcoded because this is a legit case but we don't want to load it from
// a private API. We might likely want to unify style sheet creation with how it
// is done in the DOM so this might move into React. I know what I'm doing so
// plz don't fire me.
const ReactPropTypesSecret = 'SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED'
class StyleSheetValidation {
static validateStyleProp (prop, style, caller) {
if (allStylePropTypes[prop] === undefined) {
const message1 = '"' + prop + '" is not a valid style property.'
const message2 =
'\nValid style props: ' +
JSON.stringify(Object.keys(allStylePropTypes).sort(), null, ' ')
styleError(message1, style, caller, message2)
}
const error = allStylePropTypes[prop](
style,
prop,
caller,
'prop',
null,
ReactPropTypesSecret
)
if (error) {
styleError(error.message, style, caller)
}
}
static validateStyle (name, styles) {
for (const prop in styles[name]) {
StyleSheetValidation.validateStyleProp(
prop,
styles[name],
'StyleSheet ' + name
)
}
}
static addValidStylePropTypes (stylePropTypes) {
for (const key in stylePropTypes) {
allStylePropTypes[key] = stylePropTypes[key]
}
}
}
const styleError = function (message1, style, caller, message2) {
invariant(
false,
message1 +
'\n' +
(caller || '<<unknown>>') +
': ' +
JSON.stringify(style, null, ' ') +
(message2 || '')
)
}
const allStylePropTypes = {}
StyleSheetValidation.addValidStylePropTypes(ImageStylePropTypes)
StyleSheetValidation.addValidStylePropTypes(TextStylePropTypes)
StyleSheetValidation.addValidStylePropTypes(ViewStylePropTypes)
module.exports = StyleSheetValidation

View File

@ -0,0 +1,123 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*
*/
'use strict'
const ColorPropType = require('./ColorPropType')
const ReactPropTypes = require('prop-types')
const ViewStylePropTypes = require('./ViewStylePropTypes')
const TextStylePropTypes = {
...ViewStylePropTypes,
color: ColorPropType,
fontFamily: ReactPropTypes.string,
fontSize: ReactPropTypes.number,
fontStyle: ReactPropTypes.oneOf(['normal', 'italic']),
/**
* Specifies font weight. The values 'normal' and 'bold' are supported for
* most fonts. Not all fonts have a variant for each of the numeric values,
* in that case the closest one is chosen.
*/
fontWeight: ReactPropTypes.oneOf([
'normal' /* default */,
'bold',
'100',
'200',
'300',
'400',
'500',
'600',
'700',
'800',
'900'
]),
/**
* @platform ios
*/
fontVariant: ReactPropTypes.arrayOf(
ReactPropTypes.oneOf([
'small-caps',
'oldstyle-nums',
'lining-nums',
'tabular-nums',
'proportional-nums'
])
),
textShadowOffset: ReactPropTypes.shape({
width: ReactPropTypes.number,
height: ReactPropTypes.number
}),
textShadowRadius: ReactPropTypes.number,
textShadowColor: ColorPropType,
/**
* @platform ios
*/
letterSpacing: ReactPropTypes.number,
lineHeight: ReactPropTypes.number,
/**
* Specifies text alignment. The value 'justify' is only supported on iOS and
* fallbacks to `left` on Android.
*/
textAlign: ReactPropTypes.oneOf([
'auto' /* default */,
'left',
'right',
'center',
'justify'
]),
/**
* @platform android
*/
textAlignVertical: ReactPropTypes.oneOf([
'auto' /* default */,
'top',
'bottom',
'center'
]),
/**
* Set to `false` to remove extra font padding intended to make space for certain ascenders / descenders.
* With some fonts, this padding can make text look slightly misaligned when centered vertically.
* For best results also set `textAlignVertical` to `center`. Default is true.
* @platform android
*/
includeFontPadding: ReactPropTypes.bool,
textDecorationLine: ReactPropTypes.oneOf([
'none' /* default */,
'underline',
'line-through',
'underline line-through'
]),
/**
* @platform ios
*/
textDecorationStyle: ReactPropTypes.oneOf([
'solid' /* default */,
'double',
'dotted',
'dashed'
]),
/**
* @platform ios
*/
textDecorationColor: ColorPropType,
textTransform: ReactPropTypes.oneOf([
'none' /* default */,
'capitalize',
'uppercase',
'lowercase'
]),
/**
* @platform ios
*/
writingDirection: ReactPropTypes.oneOf(['auto' /* default */, 'ltr', 'rtl'])
}
module.exports = TextStylePropTypes

View File

@ -0,0 +1,109 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*
*/
'use strict'
const ReactPropTypes = require('prop-types')
const deprecatedPropType = require('./deprecatedPropType')
const TransformMatrixPropType = function (
props,
propName,
componentName
) {
if (props[propName]) {
return new Error(
'The transformMatrix style property is deprecated. ' +
'Use `transform: [{ matrix: ... }]` instead.'
)
}
}
const DecomposedMatrixPropType = function (
props,
propName,
componentName
) {
if (props[propName]) {
return new Error(
'The decomposedMatrix style property is deprecated. ' +
'Use `transform: [...]` instead.'
)
}
}
const TransformPropTypes = {
/**
* `transform` accepts an array of transformation objects. Each object specifies
* the property that will be transformed as the key, and the value to use in the
* transformation. Objects should not be combined. Use a single key/value pair
* per object.
*
* The rotate transformations require a string so that the transform may be
* expressed in degrees (deg) or radians (rad). For example:
*
* `transform([{ rotateX: '45deg' }, { rotateZ: '0.785398rad' }])`
*
* The skew transformations require a string so that the transform may be
* expressed in degrees (deg). For example:
*
* `transform([{ skewX: '45deg' }])`
*/
transform: ReactPropTypes.arrayOf(
ReactPropTypes.oneOfType([
ReactPropTypes.shape({perspective: ReactPropTypes.number}),
ReactPropTypes.shape({rotate: ReactPropTypes.string}),
ReactPropTypes.shape({rotateX: ReactPropTypes.string}),
ReactPropTypes.shape({rotateY: ReactPropTypes.string}),
ReactPropTypes.shape({rotateZ: ReactPropTypes.string}),
ReactPropTypes.shape({scale: ReactPropTypes.number}),
ReactPropTypes.shape({scaleX: ReactPropTypes.number}),
ReactPropTypes.shape({scaleY: ReactPropTypes.number}),
ReactPropTypes.shape({translateX: ReactPropTypes.number}),
ReactPropTypes.shape({translateY: ReactPropTypes.number}),
ReactPropTypes.shape({skewX: ReactPropTypes.string}),
ReactPropTypes.shape({skewY: ReactPropTypes.string})
])
),
/**
* Deprecated. Use the transform prop instead.
*/
transformMatrix: TransformMatrixPropType,
/**
* Deprecated. Use the transform prop instead.
*/
decomposedMatrix: DecomposedMatrixPropType,
/* Deprecated transform props used on Android only */
scaleX: deprecatedPropType(
ReactPropTypes.number,
'Use the transform prop instead.'
),
scaleY: deprecatedPropType(
ReactPropTypes.number,
'Use the transform prop instead.'
),
rotation: deprecatedPropType(
ReactPropTypes.number,
'Use the transform prop instead.'
),
translateX: deprecatedPropType(
ReactPropTypes.number,
'Use the transform prop instead.'
),
translateY: deprecatedPropType(
ReactPropTypes.number,
'Use the transform prop instead.'
)
}
module.exports = TransformPropTypes

View File

@ -0,0 +1,61 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*
*/
'use strict'
const ColorPropType = require('./ColorPropType')
const LayoutPropTypes = require('./LayoutPropTypes')
const ReactPropTypes = require('prop-types')
const ShadowPropTypesIOS = require('./ShadowPropTypesIOS')
const TransformPropTypes = require('./TransformPropTypes')
/**
* Warning: Some of these properties may not be supported in all releases.
*/
const ViewStylePropTypes = {
...LayoutPropTypes,
...ShadowPropTypesIOS,
...TransformPropTypes,
backfaceVisibility: ReactPropTypes.oneOf(['visible', 'hidden']),
backgroundColor: ColorPropType,
borderColor: ColorPropType,
borderTopColor: ColorPropType,
borderRightColor: ColorPropType,
borderBottomColor: ColorPropType,
borderLeftColor: ColorPropType,
borderStartColor: ColorPropType,
borderEndColor: ColorPropType,
borderRadius: ReactPropTypes.number,
borderTopLeftRadius: ReactPropTypes.number,
borderTopRightRadius: ReactPropTypes.number,
borderTopStartRadius: ReactPropTypes.number,
borderTopEndRadius: ReactPropTypes.number,
borderBottomLeftRadius: ReactPropTypes.number,
borderBottomRightRadius: ReactPropTypes.number,
borderBottomStartRadius: ReactPropTypes.number,
borderBottomEndRadius: ReactPropTypes.number,
borderStyle: ReactPropTypes.oneOf(['solid', 'dotted', 'dashed']),
borderWidth: ReactPropTypes.number,
borderTopWidth: ReactPropTypes.number,
borderRightWidth: ReactPropTypes.number,
borderBottomWidth: ReactPropTypes.number,
borderLeftWidth: ReactPropTypes.number,
opacity: ReactPropTypes.number,
/**
* (Android-only) Sets the elevation of a view, using Android's underlying
* [elevation API](https://developer.android.com/training/material/shadows-clipping.html#Elevation).
* This adds a drop shadow to the item and affects z-order for overlapping views.
* Only supported on Android 5.0+, has no effect on earlier versions.
* @platform android
*/
elevation: ReactPropTypes.number
}
module.exports = ViewStylePropTypes

View File

@ -0,0 +1,35 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* strict-local
*/
'use strict'
// const UIManager = require('UIManager')
/**
* Adds a deprecation warning when the prop is used.
*/
function deprecatedPropType (
propType,
explanation
) {
return function validate (props, propName, componentName, ...rest) {
// Don't warn for native components.
// if (!UIManager[componentName] && props[propName] !== undefined) {
if (props[propName] !== undefined) {
console.warn(
`\`${propName}\` supplied to \`${componentName}\` has been deprecated. ${explanation}`
)
}
return propType(props, propName, componentName, ...rest)
}
}
module.exports = deprecatedPropType

View File

@ -0,0 +1,3 @@
const StyleSheetValidation = require('./StyleSheetValidation')
module.exports = {StyleSheetValidation}

View File

@ -0,0 +1,372 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*
*/
/* eslint no-bitwise: 0 */
'use strict'
function normalizeColor (color) {
const matchers = getMatchers()
let match
if (typeof color === 'number') {
if (color >>> 0 === color && color >= 0 && color <= 0xffffffff) {
return color
}
return null
}
// Ordered based on occurrences on Facebook codebase
if ((match = matchers.hex6.exec(color))) {
return parseInt(match[1] + 'ff', 16) >>> 0
}
if (names.hasOwnProperty(color)) {
return names[color]
}
if ((match = matchers.rgb.exec(color))) {
return (
// b
((parse255(match[1]) << 24) | // r
(parse255(match[2]) << 16) | // g
(parse255(match[3]) << 8) |
0x000000ff) >>> // a
0
)
}
if ((match = matchers.rgba.exec(color))) {
return (
// b
((parse255(match[1]) << 24) | // r
(parse255(match[2]) << 16) | // g
(parse255(match[3]) << 8) |
parse1(match[4])) >>> // a
0
)
}
if ((match = matchers.hex3.exec(color))) {
return (
parseInt(
match[1] +
match[1] + // r
match[2] +
match[2] + // g
match[3] +
match[3] + // b
'ff', // a
16
) >>> 0
)
}
// https://drafts.csswg.org/css-color-4/#hex-notation
if ((match = matchers.hex8.exec(color))) {
return parseInt(match[1], 16) >>> 0
}
if ((match = matchers.hex4.exec(color))) {
return (
parseInt(
match[1] +
match[1] + // r
match[2] +
match[2] + // g
match[3] +
match[3] + // b
match[4] +
match[4], // a
16
) >>> 0
)
}
if ((match = matchers.hsl.exec(color))) {
return (
(hslToRgb(
parse360(match[1]), // h
parsePercentage(match[2]), // s
parsePercentage(match[3]) // l
) |
0x000000ff) >>> // a
0
)
}
if ((match = matchers.hsla.exec(color))) {
return (
(hslToRgb(
parse360(match[1]), // h
parsePercentage(match[2]), // s
parsePercentage(match[3]) // l
) |
parse1(match[4])) >>> // a
0
)
}
return null
}
function hue2rgb (p, q, t) {
if (t < 0) {
t += 1
}
if (t > 1) {
t -= 1
}
if (t < 1 / 6) {
return p + (q - p) * 6 * t
}
if (t < 1 / 2) {
return q
}
if (t < 2 / 3) {
return p + (q - p) * (2 / 3 - t) * 6
}
return p
}
function hslToRgb (h, s, l) {
const q = l < 0.5 ? l * (1 + s) : l + s - l * s
const p = 2 * l - q
const r = hue2rgb(p, q, h + 1 / 3)
const g = hue2rgb(p, q, h)
const b = hue2rgb(p, q, h - 1 / 3)
return (
(Math.round(r * 255) << 24) |
(Math.round(g * 255) << 16) |
(Math.round(b * 255) << 8)
)
}
// var INTEGER = '[-+]?\\d+';
const NUMBER = '[-+]?\\d*\\.?\\d+'
const PERCENTAGE = NUMBER + '%'
function call (...args) {
return '\\(\\s*(' + args.join(')\\s*,\\s*(') + ')\\s*\\)'
}
let cachedMatchers
function getMatchers () {
if (cachedMatchers === undefined) {
cachedMatchers = {
rgb: new RegExp('rgb' + call(NUMBER, NUMBER, NUMBER)),
rgba: new RegExp('rgba' + call(NUMBER, NUMBER, NUMBER, NUMBER)),
hsl: new RegExp('hsl' + call(NUMBER, PERCENTAGE, PERCENTAGE)),
hsla: new RegExp('hsla' + call(NUMBER, PERCENTAGE, PERCENTAGE, NUMBER)),
hex3: /^#([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
hex4: /^#([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
hex6: /^#([0-9a-fA-F]{6})$/,
hex8: /^#([0-9a-fA-F]{8})$/
}
}
return cachedMatchers
}
function parse255 (str) {
const int = parseInt(str, 10)
if (int < 0) {
return 0
}
if (int > 255) {
return 255
}
return int
}
function parse360 (str) {
const int = parseFloat(str)
return (((int % 360) + 360) % 360) / 360
}
function parse1 (str) {
const num = parseFloat(str)
if (num < 0) {
return 0
}
if (num > 1) {
return 255
}
return Math.round(num * 255)
}
function parsePercentage (str) {
// parseFloat conveniently ignores the final %
const int = parseFloat(str)
if (int < 0) {
return 0
}
if (int > 100) {
return 1
}
return int / 100
}
const names = {
transparent: 0x00000000,
// http://www.w3.org/TR/css3-color/#svg-color
aliceblue: 0xf0f8ffff,
antiquewhite: 0xfaebd7ff,
aqua: 0x00ffffff,
aquamarine: 0x7fffd4ff,
azure: 0xf0ffffff,
beige: 0xf5f5dcff,
bisque: 0xffe4c4ff,
black: 0x000000ff,
blanchedalmond: 0xffebcdff,
blue: 0x0000ffff,
blueviolet: 0x8a2be2ff,
brown: 0xa52a2aff,
burlywood: 0xdeb887ff,
burntsienna: 0xea7e5dff,
cadetblue: 0x5f9ea0ff,
chartreuse: 0x7fff00ff,
chocolate: 0xd2691eff,
coral: 0xff7f50ff,
cornflowerblue: 0x6495edff,
cornsilk: 0xfff8dcff,
crimson: 0xdc143cff,
cyan: 0x00ffffff,
darkblue: 0x00008bff,
darkcyan: 0x008b8bff,
darkgoldenrod: 0xb8860bff,
darkgray: 0xa9a9a9ff,
darkgreen: 0x006400ff,
darkgrey: 0xa9a9a9ff,
darkkhaki: 0xbdb76bff,
darkmagenta: 0x8b008bff,
darkolivegreen: 0x556b2fff,
darkorange: 0xff8c00ff,
darkorchid: 0x9932ccff,
darkred: 0x8b0000ff,
darksalmon: 0xe9967aff,
darkseagreen: 0x8fbc8fff,
darkslateblue: 0x483d8bff,
darkslategray: 0x2f4f4fff,
darkslategrey: 0x2f4f4fff,
darkturquoise: 0x00ced1ff,
darkviolet: 0x9400d3ff,
deeppink: 0xff1493ff,
deepskyblue: 0x00bfffff,
dimgray: 0x696969ff,
dimgrey: 0x696969ff,
dodgerblue: 0x1e90ffff,
firebrick: 0xb22222ff,
floralwhite: 0xfffaf0ff,
forestgreen: 0x228b22ff,
fuchsia: 0xff00ffff,
gainsboro: 0xdcdcdcff,
ghostwhite: 0xf8f8ffff,
gold: 0xffd700ff,
goldenrod: 0xdaa520ff,
gray: 0x808080ff,
green: 0x008000ff,
greenyellow: 0xadff2fff,
grey: 0x808080ff,
honeydew: 0xf0fff0ff,
hotpink: 0xff69b4ff,
indianred: 0xcd5c5cff,
indigo: 0x4b0082ff,
ivory: 0xfffff0ff,
khaki: 0xf0e68cff,
lavender: 0xe6e6faff,
lavenderblush: 0xfff0f5ff,
lawngreen: 0x7cfc00ff,
lemonchiffon: 0xfffacdff,
lightblue: 0xadd8e6ff,
lightcoral: 0xf08080ff,
lightcyan: 0xe0ffffff,
lightgoldenrodyellow: 0xfafad2ff,
lightgray: 0xd3d3d3ff,
lightgreen: 0x90ee90ff,
lightgrey: 0xd3d3d3ff,
lightpink: 0xffb6c1ff,
lightsalmon: 0xffa07aff,
lightseagreen: 0x20b2aaff,
lightskyblue: 0x87cefaff,
lightslategray: 0x778899ff,
lightslategrey: 0x778899ff,
lightsteelblue: 0xb0c4deff,
lightyellow: 0xffffe0ff,
lime: 0x00ff00ff,
limegreen: 0x32cd32ff,
linen: 0xfaf0e6ff,
magenta: 0xff00ffff,
maroon: 0x800000ff,
mediumaquamarine: 0x66cdaaff,
mediumblue: 0x0000cdff,
mediumorchid: 0xba55d3ff,
mediumpurple: 0x9370dbff,
mediumseagreen: 0x3cb371ff,
mediumslateblue: 0x7b68eeff,
mediumspringgreen: 0x00fa9aff,
mediumturquoise: 0x48d1ccff,
mediumvioletred: 0xc71585ff,
midnightblue: 0x191970ff,
mintcream: 0xf5fffaff,
mistyrose: 0xffe4e1ff,
moccasin: 0xffe4b5ff,
navajowhite: 0xffdeadff,
navy: 0x000080ff,
oldlace: 0xfdf5e6ff,
olive: 0x808000ff,
olivedrab: 0x6b8e23ff,
orange: 0xffa500ff,
orangered: 0xff4500ff,
orchid: 0xda70d6ff,
palegoldenrod: 0xeee8aaff,
palegreen: 0x98fb98ff,
paleturquoise: 0xafeeeeff,
palevioletred: 0xdb7093ff,
papayawhip: 0xffefd5ff,
peachpuff: 0xffdab9ff,
peru: 0xcd853fff,
pink: 0xffc0cbff,
plum: 0xdda0ddff,
powderblue: 0xb0e0e6ff,
purple: 0x800080ff,
rebeccapurple: 0x663399ff,
red: 0xff0000ff,
rosybrown: 0xbc8f8fff,
royalblue: 0x4169e1ff,
saddlebrown: 0x8b4513ff,
salmon: 0xfa8072ff,
sandybrown: 0xf4a460ff,
seagreen: 0x2e8b57ff,
seashell: 0xfff5eeff,
sienna: 0xa0522dff,
silver: 0xc0c0c0ff,
skyblue: 0x87ceebff,
slateblue: 0x6a5acdff,
slategray: 0x708090ff,
slategrey: 0x708090ff,
snow: 0xfffafaff,
springgreen: 0x00ff7fff,
steelblue: 0x4682b4ff,
tan: 0xd2b48cff,
teal: 0x008080ff,
thistle: 0xd8bfd8ff,
tomato: 0xff6347ff,
turquoise: 0x40e0d0ff,
violet: 0xee82eeff,
wheat: 0xf5deb3ff,
white: 0xffffffff,
whitesmoke: 0xf5f5f5ff,
yellow: 0xffff00ff,
yellowgreen: 0x9acd32ff
}
module.exports = normalizeColor

View File

@ -0,0 +1,15 @@
{
"name":"<%= projectName %>",
"main": "./bin/crna-entry.js",
"dependencies": {
"@tarojs/components-rn": "^<%= version %>",
"@tarojs/taro-rn": "^<%= version %>",
"@tarojs/taro-router-rn": "^<%= version %>",
"@tarojs/taro-redux-rn": "^<%= version %>",
"expo": "27.0.1",
"react": "16.3.1",
"react-native": "0.55.2",
"redux": "^4.0.0",
"tslib": "^1.8.0"
}
}

View File

@ -0,0 +1,109 @@
const path = require('path')
const fs = require('fs-extra')
const postcss = require('postcss')
const chalk = require('chalk')
const pxtransform = require('postcss-pxtransform')
const transformCSS = require('css-to-react-native-transform').default
const {StyleSheetValidation} = require('./StyleSheet/index')
const Util = require('../util')
const npmProcess = require('../util/npm')
const DEVICE_RATIO = 'deviceRatio'
/**
* @description 读取 css/scss/less 文件预处理后返回 css string
* @param {string}filePath
* @param {object} pluginsConfig
* @returns {*}
*/
function loadStyle ({filePath, pluginsConfig}) {
const fileExt = path.extname(filePath)
const pluginName = Util.FILE_PROCESSOR_MAP[fileExt]
if (pluginName) {
return npmProcess.callPlugin(pluginName, null, filePath, pluginsConfig[pluginName] || {})
.then((item) => {
return {
css: item.css.toString(),
filePath
}
}).catch((e) => {
Util.printLog(Util.pocessTypeEnum.ERROR, '样式预处理', filePath)
console.log(e.stack)
})
}
return new Promise((resolve, reject) => {
fs.readFile(filePath, 'utf-8', (err, content) => {
if (err) {
return reject(err)
}
resolve({
css: content,
filePath
})
})
})
}
/**
* @description 传入 css string 返回 postCSS 处理后的 css string
* @param {string} css
* @param {string} filePath
* @param {object} projectConfig
* @returns {Function | any}
*/
function postCSS ({css, filePath, projectConfig}) {
let pxTransformConfig = {
designWidth: projectConfig.designWidth || 750
}
if (projectConfig.hasOwnProperty(DEVICE_RATIO)) {
pxTransformConfig[DEVICE_RATIO] = projectConfig.deviceRatio
}
return postcss(pxtransform({
platform: 'rn',
...pxTransformConfig
}))
.process(css, {from: filePath})
.then((result) => {
return {
css: result.css,
filePath
}
})
}
function getStyleObject ({css, filePath}) {
var styleObject = {}
try {
styleObject = transformCSS(css)
} catch (err) {
Util.printLog(Util.pocessTypeEnum.WARNING, 'css-to-react-native 报错', filePath)
console.log(chalk.red(err.stack))
}
return styleObject
}
function validateStyle ({styleObject, filePath}) {
for (let name in styleObject) {
try {
StyleSheetValidation.validateStyle(name, styleObject)
} catch (err) {
Util.printLog(Util.pocessTypeEnum.WARNING, '样式不支持', filePath)
console.log(chalk.red(err.message))
}
}
}
function writeStyleFile ({css, tempFilePath}) {
const fileContent = `import { StyleSheet } from 'react-native'\n\nexport default StyleSheet.create(${css})`
fs.ensureDirSync(path.dirname(tempFilePath))
fs.writeFileSync(tempFilePath, fileContent)
Util.printLog(Util.pocessTypeEnum.GENERATE, '生成文件', tempFilePath)
}
module.exports = {
loadStyle,
postCSS,
getStyleObject,
validateStyle,
writeStyleFile
}

View File

@ -0,0 +1,489 @@
const path = require('path')
const babel = require('babel-core')
const traverse = require('babel-traverse').default
const t = require('babel-types')
const _ = require('lodash')
const generate = require('babel-generator').default
const template = require('babel-template')
const wxTransformer = require('../../../taro-transformer-wx/lib/src/index.js').default
const Util = require('../util')
const babylonConfig = require('../config/babylon')
const {source: toAst} = require('../util/ast_convert')
const reactImportDefaultName = 'React'
let taroImportDefaultName // import default from @tarojs/taro
let componentClassName // get app.js class name
const providerComponentName = 'Provider'
const setStoreFuncName = 'setStore'
const routerImportDefaultName = 'TaroRouter'
const DEVICE_RATIO = 'deviceRatio'
const taroApis = [
'getEnv',
'ENV_TYPE',
'eventCenter',
'Events',
'internal_safe_get',
'internal_dynamic_recursive'
]
const PACKAGES = {
'@tarojs/taro': '@tarojs/taro',
'@tarojs/taro-rn': '@tarojs/taro-rn',
'@tarojs/taro-router-rn': '@tarojs/taro-router-rn',
'@tarojs/redux': '@tarojs/redux',
'@tarojs/components': '@tarojs/components',
'@tarojs/components-rn': '@tarojs/components-rn',
'react': 'react',
'react-native': 'react-native',
'react-redux-rn': '@tarojs/taro-redux-rn',
'@tarojs/mobx': '@tarojs/mobx',
'@tarojs/mobx-rn': '@tarojs/mobx-rn'
}
function getInitPxTransformNode (projectConfig) {
let pxTransformConfig = {designWidth: projectConfig.designWidth || 750}
if (projectConfig.hasOwnProperty(DEVICE_RATIO)) {
pxTransformConfig[DEVICE_RATIO] = projectConfig.deviceRatio
}
const initPxTransformNode = toAst(`Taro.initPxTransform(${JSON.stringify(pxTransformConfig)})`)
return initPxTransformNode
}
function getClassPropertyVisitor ({filePath, pages, iconPaths, isEntryFile}) {
return (astPath) => {
const node = astPath.node
const key = node.key
const value = node.value
if (key.name !== 'config' || !t.isObjectExpression(value)) return
// 入口文件的 config ,与页面的分开处理
if (isEntryFile) {
// 读取 config 配置
astPath.traverse({
ObjectProperty (astPath) {
const node = astPath.node
const key = node.key
const value = node.value
// if (key.name !== 'pages' || !t.isArrayExpression(value)) return
if (key.name === 'pages' && t.isArrayExpression(value)) {
// 分包
let root = ''
const rootNode = astPath.parent.properties.find(v => {
return v.key.name === 'root'
})
root = rootNode ? rootNode.value.value : ''
value.elements.forEach(v => {
const pagePath = `${root}/${v.value}`.replace(/\/{2,}/g, '/')
pages.push(pagePath.replace(/^\//, ''))
})
astPath.remove()
}
// window
if (key.name === 'window' && t.isObjectExpression(value)) {
return
}
if (key.name === 'tabBar' && t.isObjectExpression(value)) {
astPath.traverse({
ObjectProperty (astPath) {
let node = astPath.node
let value = node.value.value
if (node.key.name === 'iconPath' ||
node.key.value === 'iconPath' ||
node.key.name === 'selectedIconPath' ||
node.key.value === 'selectedIconPath'
) {
if (typeof value !== 'string') return
let iconName = _.camelCase(value.split('/'))
if (iconPaths.indexOf(value) === -1) {
iconPaths.push(value)
}
astPath.insertAfter(t.objectProperty(
t.identifier(node.key.name || node.key.value),
t.identifier(iconName)
))
astPath.remove()
}
}
})
}
}
})
}
astPath.node.static = 'true'
}
}
function getJSAst (code, filePath) {
return wxTransformer({
code,
sourcePath: filePath,
isNormal: true,
isTyped: Util.REG_TYPESCRIPT.test(filePath),
adapter: 'rn'
}).ast
}
/**
* TS 编译器会把 class property 移到构造器
* 而小程序要求 `config` 和所有函数在初始化(after new Class)之后就收集到所有的函数和 config 信息
* 所以当如构造器里有 this.func = () => {...} 的形式就给他转换成普通的 classProperty function
* 如果有 config 就给他还原
*/
function resetTSClassProperty (body) {
for (const method of body) {
if (t.isClassMethod(method) && method.kind === 'constructor') {
for (const statement of _.cloneDeep(method.body.body)) {
if (t.isExpressionStatement(statement) && t.isAssignmentExpression(statement.expression)) {
const expr = statement.expression
const {left, right} = expr
if (
t.isMemberExpression(left) &&
t.isThisExpression(left.object) &&
t.isIdentifier(left.property)
) {
if (
(t.isArrowFunctionExpression(right) || t.isFunctionExpression(right)) ||
(left.property.name === 'config' && t.isObjectExpression(right))
) {
body.push(
t.classProperty(left.property, right)
)
_.remove(method.body.body, statement)
}
}
}
}
}
}
}
const ClassDeclarationOrExpression = {
enter (astPath) {
const node = astPath.node
if (!node.superClass) return
if (
node.superClass.type === 'MemberExpression' &&
node.superClass.object.name === taroImportDefaultName
) {
node.superClass.object.name = taroImportDefaultName
if (node.id === null) {
const renameComponentClassName = '_TaroComponentClass'
componentClassName = renameComponentClassName
astPath.replaceWith(
t.classDeclaration(
t.identifier(renameComponentClassName),
node.superClass,
node.body,
node.decorators || []
)
)
} else {
componentClassName = node.id.name
}
} else if (node.superClass.name === 'Component') {
resetTSClassProperty(node.body.body)
if (node.id === null) {
const renameComponentClassName = '_TaroComponentClass'
componentClassName = renameComponentClassName
astPath.replaceWith(
t.classDeclaration(
t.identifier(renameComponentClassName),
node.superClass,
node.body,
node.decorators || []
)
)
} else {
componentClassName = node.id.name
}
}
}
}
function parseJSCode ({code, filePath, isEntryFile, projectConfig}) {
let ast
try {
ast = getJSAst(code, filePath)
} catch (e) {
throw e
}
const styleFiles = []
let pages = [] // app.js 里面的config 配置里面的 pages
let iconPaths = [] // app.js 里面的config 配置里面的需要引入的 iconPath
let hasAddReactImportDefaultName = false
let providorImportName
let storeName
let hasAppExportDefault
let classRenderReturnJSX
traverse(ast, {
ClassExpression: ClassDeclarationOrExpression,
ClassDeclaration: ClassDeclarationOrExpression,
ImportDeclaration (astPath) {
const node = astPath.node
const source = node.source
let value = source.value
const valueExtname = path.extname(value)
const specifiers = node.specifiers
const pathAlias = projectConfig.alias || {}
if (Util.isAliasPath(value, pathAlias)) {
source.value = value = Util.replaceAliasPath(filePath, value, pathAlias)
}
// 引入的包为 npm 包
if (!Util.isNpmPkg(value)) {
// import 样式处理
if (Util.REG_STYLE.test(valueExtname)) {
const stylePath = path.resolve(path.dirname(filePath), value)
if (styleFiles.indexOf(stylePath) < 0) {
styleFiles.push(stylePath)
}
}
return
}
if (value === PACKAGES['@tarojs/taro']) {
let specifier = specifiers.find(item => item.type === 'ImportDefaultSpecifier')
if (specifier) {
hasAddReactImportDefaultName = true
taroImportDefaultName = specifier.local.name
specifier.local.name = reactImportDefaultName
} else if (!hasAddReactImportDefaultName) {
hasAddReactImportDefaultName = true
node.specifiers.unshift(
t.importDefaultSpecifier(t.identifier(reactImportDefaultName))
)
}
// 删除从@tarojs/taro引入的 React
specifiers.forEach((item, index) => {
if (item.type === 'ImportDefaultSpecifier') {
specifiers.splice(index, 1)
}
})
const taroApisSpecifiers = []
specifiers.forEach((item, index) => {
if (item.imported && taroApis.indexOf(item.imported.name) >= 0) {
taroApisSpecifiers.push(t.importSpecifier(t.identifier(item.local.name), t.identifier(item.imported.name)))
specifiers.splice(index, 1)
}
})
source.value = PACKAGES['@tarojs/taro-rn']
// insert React
astPath.insertBefore(template(`import React from 'react'`, babylonConfig)())
if (taroApisSpecifiers.length) {
astPath.insertBefore(t.importDeclaration(taroApisSpecifiers, t.stringLiteral(PACKAGES['@tarojs/taro-rn'])))
}
if (!specifiers.length) {
astPath.remove()
}
} else if (value === PACKAGES['@tarojs/redux']) {
const specifier = specifiers.find(item => {
return t.isImportSpecifier(item) && item.imported.name === providerComponentName
})
if (specifier) {
providorImportName = specifier.local.name
} else {
providorImportName = providerComponentName
specifiers.push(t.importSpecifier(t.identifier(providerComponentName), t.identifier(providerComponentName)))
}
source.value = PACKAGES['react-redux-rn']
} else if (value === PACKAGES['@tarojs/mobx']) {
const specifier = specifiers.find(item => {
return t.isImportSpecifier(item) && item.imported.name === providerComponentName
})
if (specifier) {
providorImportName = specifier.local.name
} else {
providorImportName = providerComponentName
specifiers.push(t.importSpecifier(t.identifier(providerComponentName), t.identifier(providerComponentName)))
}
source.value = PACKAGES['@tarojs/mobx-rn']
} else if (value === PACKAGES['@tarojs/components']) {
source.value = PACKAGES['@tarojs/components-rn']
}
},
ClassProperty: getClassPropertyVisitor({filePath, pages, iconPaths, isEntryFile}),
// 获取 classRenderReturnJSX
ClassMethod (astPath) {
let node = astPath.node
const key = node.key
if (key.name !== 'render' || !isEntryFile) return
astPath.traverse({
BlockStatement (astPath) {
if (astPath.parent === node) {
node = astPath.node
astPath.traverse({
ReturnStatement (astPath) {
if (astPath.parent === node) {
astPath.traverse({
JSXElement (astPath) {
classRenderReturnJSX = generate(astPath.node).code
}
})
}
}
})
}
}
})
},
ExportDefaultDeclaration () {
if (isEntryFile) {
hasAppExportDefault = true
}
},
JSXOpeningElement: {
enter (astPath) {
if (astPath.node.name.name === 'Provider') {
for (let v of astPath.node.attributes) {
if (v.name.name !== 'store') continue
storeName = v.value.expression.name
break
}
}
}
},
Program: {
exit (astPath) {
const node = astPath.node
astPath.traverse({
ClassMethod (astPath) {
const node = astPath.node
const key = node.key
if (key.name !== 'render' || !isEntryFile) return
let funcBody = classRenderReturnJSX
if (pages.length > 0) {
funcBody = `<RootStack/>`
}
if (providerComponentName && storeName) {
// 使用redux 或 mobx
funcBody = `
<${providorImportName} store={${storeName}}>
${funcBody}
</${providorImportName}>`
}
node.body = template(`{return (${funcBody});}`, babylonConfig)()
},
CallExpression (astPath) {
const node = astPath.node
const callee = node.callee
const calleeName = callee.name
const parentPath = astPath.parentPath
if (t.isMemberExpression(callee)) {
if (callee.object.name === taroImportDefaultName && callee.property.name === 'render') {
astPath.remove()
}
} else {
if (calleeName === setStoreFuncName) {
if (parentPath.isAssignmentExpression() ||
parentPath.isExpressionStatement() ||
parentPath.isVariableDeclarator()) {
parentPath.remove()
}
}
}
}
})
// import Taro from @tarojs/taro-rn
if (taroImportDefaultName) {
const importTaro = template(
`import ${taroImportDefaultName} from '${PACKAGES['@tarojs/taro-rn']}'`,
babylonConfig
)()
node.body.unshift(importTaro)
}
if (isEntryFile) {
// 注入 import page from 'XXX'
pages.forEach(item => {
const pagePath = item.startsWith('/') ? item : `/${item}`
const screenName = _.camelCase(pagePath.split('/'), {pascalCase: true})
const importScreen = template(
`import ${screenName} from '.${pagePath}'`,
babylonConfig
)()
node.body.unshift(importScreen)
})
iconPaths.forEach(item => {
const iconPath = item.startsWith('/') ? item : `/${item}`
const iconName = _.camelCase(iconPath.split('/'))
const importIcon = template(
`import ${iconName} from '.${iconPath}'`,
babylonConfig
)()
node.body.unshift(importIcon)
})
// Taro.initRouter 生成 RootStack
const routerPages = pages
.map(item => {
const pagePath = item.startsWith('/') ? item : `/${item}`
const screenName = _.camelCase(pagePath.split('/'), {pascalCase: true})
return `['${item}',${screenName}]`
})
.join(',')
node.body.push(template(
`const RootStack = ${routerImportDefaultName}.initRouter(
[${routerPages}],
${taroImportDefaultName},
App.config
)`,
babylonConfig
)())
// initNativeApi
const initNativeApi = template(
`${taroImportDefaultName}.initNativeApi(${taroImportDefaultName})`,
babylonConfig
)()
node.body.push(initNativeApi)
// import @tarojs/taro-router-rn
const importTaroRouter = template(
`import TaroRouter from '${PACKAGES['@tarojs/taro-router-rn']}'`,
babylonConfig
)()
node.body.unshift(importTaroRouter)
// Taro.initPxTransform
node.body.push(getInitPxTransformNode(projectConfig))
// export default App
if (!hasAppExportDefault) {
const appExportDefault = template(
`export default ${componentClassName}`,
babylonConfig
)()
node.body.push(appExportDefault)
}
}
}
}
})
try {
const constantsReplaceList = Object.assign({
'process.env.TARO_ENV': Util.BUILD_TYPES.RN
}, Util.generateEnvList(projectConfig.env || {}), Util.generateConstantsList(projectConfig.defineConstants || {}))
// TODO 使用 babel-plugin-transform-jsx-to-stylesheet 处理 JSX 里面样式的处理,删除无效的样式引入待优化
ast = babel.transformFromAst(ast, code, {
plugins: [
[require('babel-plugin-transform-jsx-to-stylesheet'), {filePath}],
require('babel-plugin-transform-decorators-legacy').default,
require('babel-plugin-transform-class-properties'),
[require('babel-plugin-danger-remove-unused-import'), {ignore: ['@tarojs/taro', 'react', 'react-native', 'nervjs']}],
[require('babel-plugin-transform-define').default, constantsReplaceList]
]
}).ast
} catch (e) {
throw e
}
return {
code: unescape(generate(ast).code.replace(/\\u/g, '%u')),
styleFiles
}
}
module.exports = {transformJSCode: parseJSCode}

View File

@ -0,0 +1,431 @@
const fs = require('fs-extra')
const path = require('path')
const chokidar = require('chokidar')
const chalk = require('chalk')
const wxTransformer = require('../../taro-transformer-wx/lib/src/index.js').default
const traverse = require('babel-traverse').default
const t = require('babel-types')
const generate = require('babel-generator').default
const _ = require('lodash')
const { processFiles } = require('./h5')
const npmProcess = require('./util/npm')
const CONFIG = require('./config')
const {
resolveScriptPath,
resolveStylePath,
printLog,
pocessTypeEnum,
PROJECT_CONFIG,
BUILD_TYPES,
REG_STYLE,
REG_TYPESCRIPT,
cssImports
} = require('./util')
const appPath = process.cwd()
const configDir = path.join(appPath, PROJECT_CONFIG)
const projectConfig = require(configDir)(_.merge)
const sourceDirName = projectConfig.sourceRoot || CONFIG.SOURCE_DIR
let outputDirName = projectConfig.outputRoot || CONFIG.OUTPUT_DIR
const sourceDir = path.join(appPath, sourceDirName)
const entryFilePath = resolveScriptPath(path.join(sourceDir, 'index'))
const entryFileName = path.basename(entryFilePath)
const tempDir = '.temp'
const tempPath = path.join(appPath, tempDir)
const weappOutputName = 'weapp'
const h5OutputName = 'h5'
async function buildH5Script () {
const h5Config = projectConfig.h5 || {}
const entryFile = path.basename(entryFileName, path.extname(entryFileName)) + '.js'
outputDirName = `${outputDirName}/${h5OutputName}`
h5Config.env = projectConfig.env
h5Config.defineConstants = projectConfig.defineConstants
h5Config.plugins = projectConfig.plugins
h5Config.designWidth = projectConfig.designWidth
if (projectConfig.deviceRatio) {
h5Config.deviceRatio = projectConfig.deviceRatio
}
h5Config.sourceRoot = sourceDirName
h5Config.outputRoot = outputDirName
h5Config.entry = Object.assign({
app: [path.join(tempPath, entryFile)]
}, h5Config.entry)
h5Config.isWatch = false
const webpackRunner = await npmProcess.getNpmPkg('@tarojs/webpack-runner')
webpackRunner(h5Config)
}
async function buildH5Lib () {
try {
const outputDir = path.join(appPath, outputDirName, h5OutputName)
const tempEntryFilePath = resolveScriptPath(path.join(tempPath, 'index'))
const outputEntryFilePath = path.join(outputDir, path.basename(tempEntryFilePath))
const code = fs.readFileSync(tempEntryFilePath).toString()
const transformResult = wxTransformer({
code,
sourcePath: tempEntryFilePath,
outputPath: outputEntryFilePath,
isNormal: true,
isTyped: REG_TYPESCRIPT.test(tempEntryFilePath)
})
const { styleFiles, components, code: generateCode } = parseEntryAst(transformResult.ast, tempEntryFilePath)
const relativePath = path.relative(appPath, tempEntryFilePath)
printLog(pocessTypeEnum.COPY, '发现文件', relativePath)
fs.ensureDirSync(path.dirname(outputEntryFilePath))
fs.writeFileSync(outputEntryFilePath, generateCode)
if (components.length) {
components.forEach(item => {
copyFileToDist(item.path, tempPath, outputDir)
})
analyzeFiles(components.map(item => item.path), tempPath, outputDir)
}
if (styleFiles.length) {
styleFiles.forEach(item => {
copyFileToDist(item, tempPath, path.join(appPath, outputDirName))
})
analyzeStyleFilesImport(styleFiles, tempPath, path.join(appPath, outputDirName))
}
} catch (err) {
console.log(err)
}
}
function copyFileToDist (filePath, sourceDir, outputDir) {
if (!path.isAbsolute(filePath)) {
return
}
const dirname = path.dirname(filePath)
const distDirname = dirname.replace(sourceDir, outputDir)
const relativePath = path.relative(appPath, filePath)
printLog(pocessTypeEnum.COPY, '发现文件', relativePath)
fs.ensureDirSync(distDirname)
fs.copyFileSync(filePath, path.format({
dir: distDirname,
base: path.basename(filePath)
}))
}
function parseEntryAst (ast, relativeFile) {
const styleFiles = []
const components = []
const importExportName = []
let exportDefaultName = null
traverse(ast, {
ExportNamedDeclaration (astPath) {
const node = astPath.node
const specifiers = node.specifiers
const source = node.source
if (source && source.type === 'StringLiteral') {
specifiers.forEach(specifier => {
const exported = specifier.exported
components.push({
name: exported.name,
path: resolveScriptPath(path.resolve(path.dirname(relativeFile), source.value))
})
})
} else {
specifiers.forEach(specifier => {
const exported = specifier.exported
importExportName.push(exported.name)
})
}
},
ExportDefaultDeclaration (astPath) {
const node = astPath.node
const declaration = node.declaration
if (t.isIdentifier(declaration)) {
exportDefaultName = declaration.name
}
},
Program: {
exit (astPath) {
astPath.traverse({
ImportDeclaration (astPath) {
const node = astPath.node
const specifiers = node.specifiers
const source = node.source
const value = source.value
const valueExtname = path.extname(value)
if (REG_STYLE.test(valueExtname)) {
const stylePath = path.resolve(path.dirname(relativeFile), value)
if (styleFiles.indexOf(stylePath) < 0) {
styleFiles.push(stylePath)
}
astPath.remove()
} else {
if (importExportName.length) {
importExportName.forEach(nameItem => {
specifiers.forEach(specifier => {
const local = specifier.local
if (local.name === nameItem) {
components.push({
name: local.name,
path: resolveScriptPath(path.resolve(path.dirname(relativeFile), source.value))
})
}
})
})
}
if (exportDefaultName != null) {
specifiers.forEach(specifier => {
const local = specifier.local
if (local.name === exportDefaultName) {
components.push({
name: local.name,
path: resolveScriptPath(path.resolve(path.dirname(relativeFile), source.value))
})
}
})
}
}
}
})
}
}
})
const code = generate(ast).code
return {
code,
styleFiles,
components
}
}
function analyzeFiles (files, sourceDir, outputDir) {
const { parseAst } = require('./weapp')
files.forEach(file => {
if (fs.existsSync(file)) {
const code = fs.readFileSync(file).toString()
const transformResult = wxTransformer({
code,
sourcePath: file,
outputPath: file,
isNormal: true,
isTyped: REG_TYPESCRIPT.test(file)
})
const {
styleFiles,
scriptFiles,
jsonFiles,
mediaFiles
} = parseAst('NORMAL', transformResult.ast, [], file, file, true)
const resFiles = styleFiles.concat(scriptFiles, jsonFiles, mediaFiles)
if (resFiles.length) {
resFiles.forEach(item => {
copyFileToDist(item, sourceDir, outputDir)
})
}
if (scriptFiles.length) {
analyzeFiles(scriptFiles, sourceDir, outputDir)
}
if (styleFiles.length) {
analyzeStyleFilesImport(styleFiles, sourceDir, outputDir)
}
}
})
}
function analyzeStyleFilesImport (styleFiles, sourceDir, outputDir) {
styleFiles.forEach(item => {
if (!fs.existsSync(item)) {
return
}
let content = fs.readFileSync(item).toString()
content = content.replace(/(?:@import\s+)?\burl\s*\(\s*("(?:[^\\"\r\n\f]|\\[\s\S])*"|'(?:[^\\'\n\r\f]|\\[\s\S])*'|[^)}\s]+)\s*\)(\s*;?)/g, (m, $1) => {
if ($1) {
let filePath = $1.replace(/'?"?/g, '')
if (filePath.indexOf('.') === 0) {
filePath = path.resolve(path.dirname(item), filePath)
copyFileToDist(filePath, sourceDir, outputDir)
}
}
return m
})
let imports = cssImports(content)
if (imports.length > 0) {
imports = imports.map(importItem => {
const filePath = resolveStylePath(path.resolve(path.dirname(item), importItem))
copyFileToDist(filePath, sourceDir, outputDir)
return filePath
})
analyzeStyleFilesImport(imports, sourceDir, outputDir)
}
})
}
async function buildForWeapp () {
console.log()
console.log(chalk.green('开始编译小程序端组件库!'))
if (!fs.existsSync(entryFilePath)) {
console.log(chalk.red('入口文件不存在,请检查!'))
return
}
try {
const { compileDepStyles } = require('./weapp')
const outputDir = path.join(appPath, outputDirName, weappOutputName)
const outputEntryFilePath = path.join(outputDir, entryFileName)
const code = fs.readFileSync(entryFilePath).toString()
const transformResult = wxTransformer({
code,
sourcePath: entryFilePath,
outputPath: outputEntryFilePath,
isNormal: true,
isTyped: REG_TYPESCRIPT.test(entryFilePath)
})
const { styleFiles, components } = parseEntryAst(transformResult.ast, entryFilePath)
if (styleFiles.length) {
const outputStylePath = path.join(outputDir, 'css', 'index.css')
await compileDepStyles(outputStylePath, styleFiles, false)
}
const relativePath = path.relative(appPath, entryFilePath)
printLog(pocessTypeEnum.COPY, '发现文件', relativePath)
fs.ensureDirSync(path.dirname(outputEntryFilePath))
fs.copyFileSync(entryFilePath, path.format({
dir: path.dirname(outputEntryFilePath),
base: path.basename(outputEntryFilePath)
}))
if (components.length) {
components.forEach(item => {
copyFileToDist(item.path, sourceDir, outputDir)
})
analyzeFiles(components.map(item => item.path), sourceDir, outputDir)
}
} catch (err) {
console.log(err)
}
}
async function buildForH5 (buildConfig) {
const { buildTemp } = require('./h5')
console.log()
console.log(chalk.green('开始编译 H5 端组件库!'))
await buildTemp(buildConfig)
if (process.env.TARO_BUILD_TYPE === 'script') {
await buildH5Script()
} else {
await buildH5Lib()
}
}
function buildEntry () {
const content = `if (process.env.TARO_ENV === '${BUILD_TYPES.H5}') {
module.exports = require('./${h5OutputName}/index')
module.exports.default = module.exports
} else {
module.exports = require('./${weappOutputName}/index')
module.exports.default = module.exports
}`
const outputDir = path.join(appPath, outputDirName)
fs.writeFileSync(path.join(outputDir, 'index.js'), content)
}
function watchFiles () {
console.log('\n', chalk.gray('监听文件修改中...'), '\n')
const watchList = [sourceDir]
const uiConfig = projectConfig.ui || {}
const { extraWatchFiles = [] } = uiConfig
extraWatchFiles.forEach(item => {
watchList.push(path.join(appPath, item.path))
if (typeof item.handler === 'function') item.callback = item.handler({ buildH5Script })
})
const watcher = chokidar.watch(watchList, {
ignored: /(^|[/\\])\../,
ignoreInitial: true
})
function syncWeappFile (filePath) {
const outputDir = path.join(appPath, outputDirName, weappOutputName)
copyFileToDist(filePath, sourceDir, outputDir)
// 依赖分析
const extname = path.extname(filePath)
if (REG_STYLE.test(extname)) {
analyzeStyleFilesImport([filePath], sourceDir, outputDir)
} else {
analyzeFiles([filePath], sourceDir, outputDir)
}
}
function syncH5File (filePath) {
const outputDir = path.join(appPath, outputDirName, h5OutputName)
const fileTempPath = filePath.replace(sourceDir, tempPath)
processFiles(filePath)
if (process.env.TARO_BUILD_TYPE === 'script') {
buildH5Script()
} else {
copyFileToDist(fileTempPath, tempPath, outputDir)
// 依赖分析
const extname = path.extname(filePath)
if (REG_STYLE.test(extname)) {
analyzeStyleFilesImport([fileTempPath], tempPath, outputDir)
} else {
analyzeFiles([fileTempPath], tempPath, outputDir)
}
}
}
function handleChange (filePath, type, tips) {
const relativePath = path.relative(appPath, filePath)
printLog(type, tips, relativePath)
let processed = false
extraWatchFiles.forEach(item => {
if (filePath.indexOf(item.path.substr(2)) < 0) return
if (typeof item.callback === 'function') {
item.callback()
processed = true
}
})
if (processed) return
try {
syncWeappFile(filePath)
syncH5File(filePath)
} catch (err) {
console.log(err)
}
}
watcher
.on('add', filePath => handleChange(filePath, pocessTypeEnum.CREATE, '添加文件'))
.on('change', filePath => handleChange(filePath, pocessTypeEnum.MODIFY, '文件变动'))
.on('unlink', filePath => {
for (const path in extraWatchFiles) {
if (filePath.indexOf(path.substr(2)) > -1) return
}
const relativePath = path.relative(appPath, filePath)
printLog(pocessTypeEnum.UNLINK, '删除文件', relativePath)
const weappOutputPath = path.join(appPath, outputDirName, weappOutputName)
const h5OutputPath = path.join(appPath, outputDirName, h5OutputName)
const fileTempPath = filePath.replace(sourceDir, tempPath)
const fileWeappPath = filePath.replace(sourceDir, weappOutputPath)
const fileH5Path = filePath.replace(sourceDir, h5OutputPath)
fs.existsSync(fileTempPath) && fs.unlinkSync(fileTempPath)
fs.existsSync(fileWeappPath) && fs.unlinkSync(fileWeappPath)
fs.existsSync(fileH5Path) && fs.unlinkSync(fileH5Path)
})
}
async function build ({ watch }) {
buildEntry()
await buildForWeapp()
await buildForH5()
if (watch) {
watchFiles()
}
}
module.exports = {
build
}

View File

@ -0,0 +1,79 @@
const t = require('babel-types')
const babylonConfig = require('../config/babylon')
const template = require('babel-template')
const generate = require('better-babel-generator').default
function convertObjectToAstExpression (obj) {
const objArr = Object.keys(obj).map(key => {
const value = obj[key]
if (typeof value === 'string') {
return t.objectProperty(t.stringLiteral(key), t.stringLiteral(value))
}
if (typeof value === 'number') {
return t.objectProperty(t.stringLiteral(key), t.numericLiteral(value))
}
if (typeof value === 'boolean') {
return t.objectProperty(t.stringLiteral(key), t.booleanLiteral(value))
}
if (Array.isArray(value)) {
return t.objectProperty(t.stringLiteral(key), t.arrayExpression(convertArrayToAstExpression(value)))
}
if (value == null) {
return t.objectProperty(t.stringLiteral(key), t.nullLiteral())
}
if (typeof value === 'object') {
return t.objectProperty(t.stringLiteral(key), t.objectExpression(convertObjectToAstExpression(value)))
}
})
return objArr
}
// 最低限度的转义: https://github.com/mathiasbynens/jsesc#minimal
function generateMinimalEscapeCode (ast) {
return generate(ast, {
jsescOption: {
minimal: true
}
}).code
}
function convertArrayToAstExpression (arr) {
return arr.map(value => {
if (typeof value === 'string') {
return t.stringLiteral(value)
}
if (typeof value === 'number') {
return t.numericLiteral(value)
}
if (typeof value === 'boolean') {
return t.booleanLiteral(value)
}
if (Array.isArray(value)) {
return convertArrayToAstExpression(value)
}
if (value == null) {
return t.nullLiteral()
}
if (typeof value === 'object') {
return t.objectExpression(convertObjectToAstExpression(value))
}
})
}
function convertSourceStringToAstExpression (str, opts = {}) {
return template(str, Object.assign({}, babylonConfig, opts))()
}
const getObjKey = (node) => {
if (t.isIdentifier(node)) {
return node.name
} else {
return node.value
}
}
exports.obj = convertObjectToAstExpression
exports.array = convertArrayToAstExpression
exports.source = convertSourceStringToAstExpression
exports.getObjKey = getObjKey
exports.generateMinimalEscapeCode = generateMinimalEscapeCode

View File

@ -0,0 +1,602 @@
const path = require('path')
const crypto = require('crypto')
const os = require('os')
const fs = require('fs-extra')
const execSync = require('child_process').execSync
const chalk = require('chalk')
const _ = require('lodash')
const pocessTypeEnum = {
CREATE: 'create',
COMPILE: 'compile',
CONVERT: 'convert',
COPY: 'copy',
GENERATE: 'generate',
MODIFY: 'modify',
ERROR: 'error',
WARNING: 'warning',
UNLINK: 'unlink',
REFERENCE: 'reference'
}
const processTypeMap = {
[pocessTypeEnum.CREATE]: {
name: '创建',
color: 'cyan'
},
[pocessTypeEnum.COMPILE]: {
name: '编译',
color: 'green'
},
[pocessTypeEnum.CONVERT]: {
name: '转换',
color: chalk.rgb(255, 136, 0)
},
[pocessTypeEnum.COPY]: {
name: '拷贝',
color: 'magenta'
},
[pocessTypeEnum.GENERATE]: {
name: '生成',
color: 'blue'
},
[pocessTypeEnum.MODIFY]: {
name: '修改',
color: 'yellow'
},
[pocessTypeEnum.ERROR]: {
name: '错误',
color: 'red'
},
[pocessTypeEnum.WARNING]: {
name: '警告',
color: 'yellow'
},
[pocessTypeEnum.UNLINK]: {
name: '删除',
color: 'magenta'
},
[pocessTypeEnum.START]: {
name: '启动',
color: 'green'
},
[pocessTypeEnum.REFERENCE]: {
name: '引用',
color: 'blue'
}
}
exports.pocessTypeEnum = pocessTypeEnum
exports.CSS_EXT = ['.css', '.scss', '.sass', '.less', '.styl', '.wxss', '.acss']
exports.SCSS_EXT = ['.scss']
exports.JS_EXT = ['.js', '.jsx']
exports.TS_EXT = ['.ts', '.tsx']
exports.REG_JS = /\.js(\?.*)?$/
exports.REG_SCRIPT = /\.(js|jsx)(\?.*)?$/
exports.REG_TYPESCRIPT = /\.(tsx|ts)(\?.*)?$/
exports.REG_SCRIPTS = /\.[tj]sx?$/i
exports.REG_STYLE = /\.(css|scss|sass|less|styl|wxss)(\?.*)?$/
exports.REG_MEDIA = /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/
exports.REG_IMAGE = /\.(png|jpe?g|gif|bpm|svg|webp)(\?.*)?$/
exports.REG_FONT = /\.(woff2?|eot|ttf|otf)(\?.*)?$/
exports.REG_JSON = /\.json(\?.*)?$/
exports.REG_WXML_IMPORT = /<import(.*)?src=(?:(?:'([^']*)')|(?:"([^"]*)"))/gi
exports.REG_URL = /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,}))\.?)(?::\d{2,5})?(?:[/?#]\S*)?$/i
exports.CSS_IMPORT_REG = /@import (["'])(.+?)\1;/g
exports.BUILD_TYPES = {
WEAPP: 'weapp',
H5: 'h5',
RN: 'rn',
SWAN: 'swan',
ALIPAY: 'alipay',
TT: 'tt',
UI: 'ui'
}
exports.MINI_APP_FILES = {
[exports.BUILD_TYPES.WEAPP]: {
TEMPL: '.wxml',
STYLE: '.wxss',
SCRIPT: '.js',
CONFIG: '.json'
},
[exports.BUILD_TYPES.SWAN]: {
TEMPL: '.swan',
STYLE: '.css',
SCRIPT: '.js',
CONFIG: '.json'
},
[exports.BUILD_TYPES.ALIPAY]: {
TEMPL: '.axml',
STYLE: '.acss',
SCRIPT: '.js',
CONFIG: '.json'
},
[exports.BUILD_TYPES.TT]: {
TEMPL: '.ttml',
STYLE: '.ttss',
SCRIPT: '.js',
CONFIG: '.json'
}
}
exports.CONFIG_MAP = {
[exports.BUILD_TYPES.WEAPP]: {
navigationBarTitleText: 'navigationBarTitleText',
navigationBarBackgroundColor: 'navigationBarBackgroundColor',
enablePullDownRefresh: 'enablePullDownRefresh',
list: 'list',
text: 'text',
iconPath: 'iconPath',
selectedIconPath: 'selectedIconPath'
},
[exports.BUILD_TYPES.SWAN]: {
navigationBarTitleText: 'navigationBarTitleText',
navigationBarBackgroundColor: 'navigationBarBackgroundColor',
enablePullDownRefresh: 'enablePullDownRefresh',
list: 'list',
text: 'text',
iconPath: 'iconPath',
selectedIconPath: 'selectedIconPath'
},
[exports.BUILD_TYPES.TT]: {
navigationBarTitleText: 'navigationBarTitleText',
navigationBarBackgroundColor: 'navigationBarBackgroundColor',
enablePullDownRefresh: 'enablePullDownRefresh',
list: 'list',
text: 'text',
iconPath: 'iconPath',
selectedIconPath: 'selectedIconPath'
},
[exports.BUILD_TYPES.ALIPAY]: {
navigationBarTitleText: 'defaultTitle',
navigationBarBackgroundColor: 'titleBarColor',
enablePullDownRefresh: 'pullRefresh',
list: 'items',
text: 'name',
iconPath: 'icon',
selectedIconPath: 'activeIcon'
}
}
exports.PROJECT_CONFIG = 'config/index.js'
exports.DEVICE_RATIO = {
'640': 2.34 / 2,
'750': 1,
'828': 1.81 / 2
}
exports.FILE_PROCESSOR_MAP = {
'.js': 'babel',
'.scss': 'sass',
'.sass': 'sass',
'.less': 'less',
'.styl': 'stylus'
}
exports.isNpmPkg = function (name) {
if (/^(\.|\/)/.test(name)) {
return false
}
return true
}
exports.isAliasPath = function (name, pathAlias = {}) {
const prefixs = Object.keys(pathAlias)
if (prefixs.length === 0) {
return false
}
return prefixs.includes(name) || (new RegExp(`^(${prefixs.join('|')})/`).test(name))
}
exports.replaceAliasPath = function (filePath, name, pathAlias = {}) {
// 后续的 path.join 在遇到符号链接时将会解析为真实路径,如果
// 这里的 filePath 没有做同样的处理,可能会导致 import 指向
// 源代码文件,导致文件被意外修改
filePath = fs.realpathSync(filePath)
const prefixs = Object.keys(pathAlias)
if (prefixs.includes(name)) {
return exports.promoteRelativePath(path.relative(filePath, fs.realpathSync(pathAlias[name])))
}
const reg = new RegExp(`^(${prefixs.join('|')})/(.*)`)
name = name.replace(reg, function (m, $1, $2) {
return exports.promoteRelativePath(path.relative(filePath, path.join(pathAlias[$1], $2)))
})
return name
}
exports.promoteRelativePath = function (fPath) {
const fPathArr = fPath.split(path.sep)
let dotCount = 0
fPathArr.forEach(item => {
if (item.indexOf('..') >= 0) {
dotCount++
}
})
if (dotCount === 1) {
fPathArr.splice(0, 1, '.')
return fPathArr.join('/')
}
if (dotCount > 1) {
fPathArr.splice(0, 1)
return fPathArr.join('/')
}
return fPath.replace(/\\/g, '/')
}
exports.replaceAsync = async function (str, regex, asyncFn) {
const promises = []
str.replace(regex, (match, ...args) => {
const promise = asyncFn(match, ...args)
promises.push(promise)
})
const data = await Promise.all(promises)
return str.replace(regex, () => data.shift())
}
exports.homedir = (function () {
let homedir = null
const env = process.env
const home = env.HOME
const user = env.LOGNAME || env.USER || env.LNAME || env.USERNAME
if (process.platform === 'win32') {
homedir = env.USERPROFILE || env.HOMEDRIVE + env.HOMEPATH || home || null
} else if (process.platform === 'darwin') {
homedir = home || (user ? `/Users/${user}` : null)
} else if (process.platform === 'linux') {
homedir = home || (process.getuid() === 0 ? '/root' : (user ? `/home/${user}` : null))
}
return typeof os.homedir === 'function' ? os.homedir : function () {
return homedir
}
})()
exports.getRootPath = function () {
return path.resolve(__dirname, '../../')
}
exports.getTaroPath = function () {
const taroPath = path.join(exports.homedir(), '.taro')
if (!fs.existsSync(taroPath)) {
fs.mkdirSync(taroPath)
}
return taroPath
}
exports.setConfig = function (config) {
const taroPath = exports.getTaroPath()
if (typeof config === 'object') {
const oldConfig = exports.getConfig()
config = Object.assign({}, oldConfig, config)
fs.writeFileSync(path.join(taroPath, 'config.json'), JSON.stringify(config, null, 2))
}
}
exports.getConfig = function () {
const configPath = path.join(exports.getTaroPath(), 'config.json')
if (fs.existsSync(configPath)) {
return require(configPath)
}
return {}
}
exports.getSystemUsername = function () {
const userHome = exports.homedir()
const systemUsername = process.env.USER || path.basename(userHome)
return systemUsername
}
exports.getPkgVersion = function () {
return require(path.join(exports.getRootPath(), 'package.json')).version
}
exports.getPkgItemByKey = function (key) {
const packageMap = require(path.join(exports.getRootPath(), 'package.json'))
if (Object.keys(packageMap).indexOf(key) === -1) {
return {}
} else {
return packageMap[key]
}
}
exports.printPkgVersion = function () {
const taroVersion = exports.getPkgVersion()
console.log(`👽 Taro-Omi v${taroVersion}`)
console.log()
}
exports.shouldUseYarn = function () {
try {
execSync('yarn --version', { stdio: 'ignore' })
return true
} catch (e) {
return false
}
}
exports.shouldUseCnpm = function () {
try {
execSync('cnpm --version', { stdio: 'ignore' })
return true
} catch (e) {
return false
}
}
exports.isPublic = function isPublic (addr) {
return !exports.isPrivate(addr)
}
exports.isEmptyObject = function (obj) {
if (obj == null) {
return true
}
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
return false
}
}
return true
}
exports.urlJoin = function () {
function normalize (str) {
return str
.replace(/([/]+)/g, '/')
.replace(/\/\?(?!\?)/g, '?')
.replace(/\/#/g, '#')
.replace(/:\//g, '://')
}
const joined = [].slice.call(arguments, 0).join('/')
return normalize(joined)
}
exports.resolveScriptPath = function (p) {
let realPath = p
const SCRIPT_EXT = exports.JS_EXT.concat(exports.TS_EXT)
for (let i = 0; i < SCRIPT_EXT.length; i++) {
const item = SCRIPT_EXT[i]
if (fs.existsSync(`${p}${item}`)) {
return `${p}${item}`
}
if (fs.existsSync(`${p}${path.sep}index${item}`)) {
return `${p}${path.sep}index${item}`
}
}
return realPath
}
exports.resolveStylePath = function (p) {
let realPath = p
const CSS_EXT = exports.CSS_EXT
for (let i = 0; i < CSS_EXT.length; i++) {
const item = CSS_EXT[i]
if (fs.existsSync(`${p}${item}`)) {
return `${p}${item}`
}
}
return realPath
}
exports.isDifferentArray = function (a, b) {
if (!Array.isArray(a) || !Array.isArray(b)) {
return true
}
if (a.length !== b.length) {
return true
}
a = a.sort()
b = b.sort()
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) {
return true
}
}
return false
}
exports.checksum = function (buf, length) {
if (!Buffer.isBuffer(buf)) {
buf = Buffer.from(buf)
}
return crypto.createHash('md5').update(buf).digest('hex').slice(0, length || 8)
}
exports.printLog = function (type, tag, filePath) {
const typeShow = processTypeMap[type]
const tagLen = tag.replace(/[\u0391-\uFFE5]/g, 'aa').length
const tagFormatLen = 8
if (tagLen < tagFormatLen) {
const rightPadding = new Array(tagFormatLen - tagLen + 1).join(' ')
tag += rightPadding
}
const padding = ''
filePath = filePath || ''
if (typeof typeShow.color === 'string') {
console.log(chalk[typeShow.color](typeShow.name), padding, tag, padding, filePath)
} else {
console.log(typeShow.color(typeShow.name), padding, tag, padding, filePath)
}
}
exports.replaceContentEnv = function (content, env) {
if (env && !exports.isEmptyObject(env)) {
for (const key in env) {
const reg = new RegExp(`process.env.${key}`, 'g')
content = content.replace(reg, env[key])
}
return content
}
return content
}
exports.generateEnvList = function (env) {
const res = { }
if (env && !exports.isEmptyObject(env)) {
for (const key in env) {
try {
res[`process.env.${key}`] = JSON.parse(env[key])
} catch (err) {
res[`process.env.${key}`] = env[key]
}
}
}
return res
}
exports.replaceContentConstants = function (content, constants) {
if (constants && !exports.isEmptyObject(constants)) {
for (const key in constants) {
const reg = new RegExp(key, 'g')
content = content.replace(reg, constants[key])
}
return content
}
return content
}
exports.generateConstantsList = function (constants) {
const res = { }
if (constants && !exports.isEmptyObject(constants)) {
for (const key in constants) {
try {
res[key] = JSON.parse(constants[key])
} catch (err) {
res[key] = constants[key]
}
}
}
return res
}
exports.cssImports = function (content) {
let match = {}
const results = []
content = String(content).replace(/\/\*.+?\*\/|\/\/.*(?=[\n\r])/g, '')
while ((match = exports.CSS_IMPORT_REG.exec(content))) {
results.push(match[2])
}
return results
}
exports.processStyleImports = function (content, adapter, process) {
const style = []
const imports = []
const styleReg = new RegExp(`\\${exports.MINI_APP_FILES[adapter].STYLE}`)
content = content.replace(exports.CSS_IMPORT_REG, (m, $1, $2) => {
if (styleReg.test($2)) {
style.push(m)
imports.push($2)
if (process && typeof process === 'function') {
return process(m, $2)
}
return ''
}
if (process && typeof process === 'function') {
return process(m, $2)
}
return m
})
return {
content,
style,
imports
}
}
/*eslint-disable*/
const retries = (process.platform === 'win32') ? 100 : 1
exports.emptyDirectory = function (dirPath, opts = { excludes: [] }) {
if (fs.existsSync(dirPath)) {
fs.readdirSync(dirPath).forEach(file => {
const curPath = path.join(dirPath, file)
if (fs.lstatSync(curPath).isDirectory()) {
let removed = false
let i = 0 // retry counter
do {
try {
if (!opts.excludes.length || !opts.excludes.some(item => curPath.indexOf(item) >= 0)) {
exports.emptyDirectory(curPath)
fs.rmdirSync(curPath)
}
removed = true
} catch (e) {
} finally {
if (++i < retries) {
continue
}
}
} while (!removed)
} else {
fs.unlinkSync(curPath)
}
})
}
}
/* eslint-enable */
exports.recursiveFindNodeModules = function (filePath) {
const dirname = path.dirname(filePath)
const nodeModules = path.join(dirname, 'node_modules')
if (fs.existsSync(nodeModules)) {
return nodeModules
}
return exports.recursiveFindNodeModules(dirname)
}
exports.UPDATE_PACKAGE_LIST = [
'@tarojs/taro',
'@tarojs/async-await',
'@tarojs/cli',
'@tarojs/components',
'@tarojs/components-rn',
'@tarojs/taro-h5',
'@tarojs/taro-swan',
'@tarojs/taro-alipay',
'@tarojs/taro-tt',
'@tarojs/plugin-babel',
'@tarojs/plugin-csso',
'@tarojs/plugin-sass',
'@tarojs/plugin-less',
'@tarojs/plugin-stylus',
'@tarojs/plugin-uglifyjs',
'@tarojs/redux',
'@tarojs/redux-h5',
'@tarojs/taro-redux-rn',
'@tarojs/taro-router-rn',
'@tarojs/taro-rn',
'@tarojs/rn-runner',
'@tarojs/router',
'@tarojs/taro-weapp',
'@tarojs/webpack-runner',
'postcss-plugin-constparse',
'eslint-config-taro',
'eslint-plugin-taro',
'taro-transformer-wx',
'postcss-pxtransform',
'babel-plugin-transform-jsx-to-stylesheet',
'@tarojs/mobx',
'@tarojs/mobx-h5',
'@tarojs/mobx-rn',
'@tarojs/mobx-common',
'@tarojs/mobx-prop-types'
]
exports.pascalCase = (str) => str.charAt(0).toUpperCase() + _.camelCase(str.substr(1))
exports.getInstalledNpmPkgVersion = function (pkgName, basedir) {
const resolvePath = require('resolve')
try {
const pkg = resolvePath.sync(`${pkgName}/package.json`, { basedir })
const pkgJson = fs.readJSONSync(pkg)
return pkgJson.version
} catch (err) {
return null
}
}

View File

@ -0,0 +1,164 @@
const resolvePath = require('resolve')
const spawn = require('cross-spawn')
const chalk = require('chalk')
const Util = require('./')
const basedir = process.cwd()
const taroPluginPrefix = '@tarojs/plugin-'
const PEERS = /UNMET PEER DEPENDENCY ([a-z\-0-9.]+)@(.+)/gm
const npmCached = {}
const erroneous = []
const defaultInstallOptions = {
dev: false,
peerDependencies: true
}
function resolveNpm (pluginName) {
if (!npmCached[pluginName]) {
return new Promise((resolve, reject) => {
resolvePath(`${pluginName}`, {basedir}, (err, res) => {
if (err) {
return reject(err)
}
npmCached[pluginName] = res
resolve(res)
})
})
}
return Promise.resolve(npmCached[pluginName])
}
function resolveNpmSync (pluginName) {
try {
if (!npmCached[pluginName]) {
const res = resolvePath.sync(pluginName, {basedir})
return res
}
return npmCached[pluginName]
} catch (err) {
if (err.code === 'MODULE_NOT_FOUND') {
console.log(chalk.cyan(`缺少npm包${pluginName},开始安装...`))
const installOptions = {}
if (pluginName.indexOf(taroPluginPrefix) >= 0) {
installOptions.dev = true
}
installNpmPkg(pluginName, installOptions)
return resolveNpmSync(pluginName)
}
}
}
function installNpmPkg (pkgList, options) {
if (!pkgList) {
return
}
if (!Array.isArray(pkgList)) {
pkgList = [pkgList]
}
pkgList = pkgList.filter(dep => {
return erroneous.indexOf(dep) === -1
})
if (!pkgList.length) {
return
}
options = Object.assign({}, defaultInstallOptions, options)
let installer = ''
let args = []
if (Util.shouldUseYarn()) {
installer = 'yarn'
} else if (Util.shouldUseCnpm()) {
installer = 'cnpm'
} else {
installer = 'npm'
}
if (Util.shouldUseYarn()) {
args = ['add'].concat(pkgList).filter(Boolean)
args.push('--silent', '--no-progress')
if (options.dev) {
args.push('-D')
}
} else {
args = ['install'].concat(pkgList).filter(Boolean)
args.push('--silent', '--no-progress')
if (options.dev) {
args.push('--save-dev')
} else {
args.push('--save')
}
}
const output = spawn.sync(installer, args, {
stdio: ['ignore', 'pipe', 'inherit']
})
if (output.status) {
pkgList.forEach(dep => {
erroneous.push(dep)
})
}
let matches = null
const peers = []
while ((matches = PEERS.exec(output.stdout))) {
const pkg = matches[1]
const version = matches[2]
if (version.match(' ')) {
peers.push(pkg)
} else {
peers.push(`${pkg}@${version}`)
}
}
if (options.peerDependencies && peers.length) {
console.info('正在安装 peerDependencies...')
installNpmPkg(peers, options)
}
return output
}
async function callPlugin (pluginName, content, file, config) {
const pluginFn = await getNpmPkg(`${taroPluginPrefix}${pluginName}`)
return pluginFn(content, file, config)
}
function callPluginSync (pluginName, content, file, config) {
const pluginFn = getNpmPkgSync(`${taroPluginPrefix}${pluginName}`)
return pluginFn(content, file, config)
}
function getNpmPkgSync (npmName) {
const npmPath = resolveNpmSync(npmName)
const npmFn = require(npmPath)
return npmFn
}
async function getNpmPkg (npmName) {
let npmPath
try {
npmPath = resolveNpmSync(npmName)
} catch (err) {
if (err.code === 'MODULE_NOT_FOUND') {
console.log(chalk.cyan(`缺少npm包${npmName},开始安装...`))
const installOptions = {}
if (npmName.indexOf(taroPluginPrefix) >= 0) {
installOptions.dev = true
}
installNpmPkg(npmName, installOptions)
npmPath = await resolveNpm(npmName)
}
}
const npmFn = require(npmPath)
return npmFn
}
module.exports = {
taroPluginPrefix,
installNpmPkg,
resolveNpm,
resolveNpmSync,
callPlugin,
callPluginSync,
getNpmPkg,
getNpmPkgSync
}

View File

@ -0,0 +1,270 @@
const fs = require('fs-extra')
const path = require('path')
const resolvePath = require('resolve')
const wxTransformer = require('../../../taro-transformer-wx/lib/src/index.js').default
const babel = require('babel-core')
const traverse = require('babel-traverse').default
const t = require('babel-types')
const generate = require('babel-generator').default
const _ = require('lodash')
const defaultUglifyConfig = require('../config/uglify')
const {
isNpmPkg,
promoteRelativePath,
printLog,
pocessTypeEnum,
PROJECT_CONFIG,
generateEnvList,
REG_TYPESCRIPT,
BUILD_TYPES,
REG_STYLE,
recursiveFindNodeModules
} = require('./index')
const CONFIG = require('../config')
const defaultBabelConfig = require('../config/babel')
const npmProcess = require('./npm')
const excludeNpmPkgs = ['ReactPropTypes']
const resolvedCache = {}
const copyedFiles = {}
const basedir = process.cwd()
const configDir = path.join(basedir, PROJECT_CONFIG)
const projectConfig = require(configDir)(_.merge)
const pluginsConfig = projectConfig.plugins || {}
const outputDirName = projectConfig.outputRoot || CONFIG.OUTPUT_DIR
const babelConfig = _.mergeWith({}, defaultBabelConfig, pluginsConfig.babel, (objValue, srcValue) => {
if (Array.isArray(objValue)) {
return Array.from(new Set(srcValue.concat(objValue)))
}
})
function resolveNpmPkgMainPath (pkgName, isProduction, npmConfig, buildAdapter = BUILD_TYPES.WEAPP, root = basedir) {
try {
return resolvePath.sync(pkgName, { basedir: root })
} catch (err) {
if (err.code === 'MODULE_NOT_FOUND') {
console.log(`缺少npm包${pkgName},开始安装...`)
const installOptions = {}
if (pkgName.indexOf(npmProcess.taroPluginPrefix) >= 0) {
installOptions.dev = true
}
npmProcess.installNpmPkg(pkgName, installOptions)
return resolveNpmPkgMainPath(pkgName, isProduction, npmConfig, buildAdapter, root)
}
}
}
function resolveNpmFilesPath (pkgName, isProduction, npmConfig, buildAdapter = BUILD_TYPES.WEAPP, root = basedir, compileInclude = []) {
if (!resolvedCache[pkgName]) {
const res = resolveNpmPkgMainPath(pkgName, isProduction, npmConfig, buildAdapter, root)
resolvedCache[pkgName] = {
main: res,
files: []
}
resolvedCache[pkgName].files.push(res)
recursiveRequire(res, resolvedCache[pkgName].files, isProduction, npmConfig, buildAdapter, compileInclude)
}
return resolvedCache[pkgName]
}
function parseAst (ast, filePath, files, isProduction, npmConfig, buildAdapter = BUILD_TYPES.WEAPP, compileInclude) {
const excludeRequire = []
traverse(ast, {
IfStatement (astPath) {
astPath.traverse({
BinaryExpression (astPath) {
const node = astPath.node
const left = node.left
if (generate(left).code === 'process.env.TARO_ENV' &&
node.right.value !== buildAdapter) {
const consequentSibling = astPath.getSibling('consequent')
consequentSibling.traverse({
CallExpression (astPath) {
if (astPath.get('callee').isIdentifier({ name: 'require' })) {
const arg = astPath.get('arguments')[0]
if (t.isStringLiteral(arg.node)) {
excludeRequire.push(arg.node.value)
}
}
}
})
}
}
})
},
Program: {
exit (astPath) {
astPath.traverse({
CallExpression (astPath) {
const node = astPath.node
const callee = node.callee
if (callee.name === 'require') {
const args = node.arguments
let requirePath = args[0].value
if (excludeRequire.indexOf(requirePath) < 0) {
if (isNpmPkg(requirePath)) {
if (excludeNpmPkgs.indexOf(requirePath) < 0) {
const res = resolveNpmFilesPath(requirePath, isProduction, npmConfig, buildAdapter, path.dirname(recursiveFindNodeModules(filePath)), compileInclude)
let relativeRequirePath = promoteRelativePath(path.relative(filePath, res.main))
relativeRequirePath = relativeRequirePath.replace(/node_modules/g, npmConfig.name)
if (buildAdapter === BUILD_TYPES.ALIPAY) {
relativeRequirePath = relativeRequirePath.replace(/@/g, '_')
}
args[0].value = relativeRequirePath
}
} else {
let realRequirePath = path.resolve(path.dirname(filePath), requirePath)
let tempPathWithJS = `${realRequirePath}.js`
let tempPathWithIndexJS = `${realRequirePath}${path.sep}index.js`
if (fs.existsSync(tempPathWithJS)) {
realRequirePath = tempPathWithJS
requirePath += '.js'
} else if (fs.existsSync(tempPathWithIndexJS)) {
realRequirePath = tempPathWithIndexJS
requirePath += '/index.js'
}
if (files.indexOf(realRequirePath) < 0) {
files.push(realRequirePath)
recursiveRequire(realRequirePath, files, isProduction, npmConfig, buildAdapter, compileInclude)
}
args[0].value = requirePath
}
}
}
}
})
}
}
})
return generate(ast).code
}
async function recursiveRequire (filePath, files, isProduction, npmConfig = {}, buildAdapter, compileInclude = []) {
let fileContent = fs.readFileSync(filePath).toString()
let outputNpmPath
if (!npmConfig.dir) {
const cwdRelate2Npm = path.relative(
filePath.slice(0, filePath.search('node_modules')),
process.cwd()
)
outputNpmPath = filePath.replace('node_modules', path.join(cwdRelate2Npm, outputDirName, npmConfig.name))
outputNpmPath = outputNpmPath.replace(/node_modules/g, npmConfig.name)
} else {
let npmFilePath = filePath.match(/(?=(node_modules)).*/)[0]
npmFilePath = npmFilePath.replace(/node_modules/g, npmConfig.name)
outputNpmPath = path.join(path.resolve(configDir, '..', npmConfig.dir), npmFilePath)
}
if (buildAdapter === BUILD_TYPES.ALIPAY) {
outputNpmPath = outputNpmPath.replace(/@/g, '_')
}
if (REG_STYLE.test(path.basename(filePath))) {
return
}
fileContent = npmCodeHack(filePath, fileContent, buildAdapter)
try {
const constantsReplaceList = Object.assign({
'process.env.TARO_ENV': buildAdapter
}, generateEnvList(projectConfig.env || {}))
const transformResult = wxTransformer({
code: fileContent,
sourcePath: filePath,
outputPath: outputNpmPath,
isNormal: true,
adapter: buildAdapter,
isTyped: REG_TYPESCRIPT.test(filePath),
env: constantsReplaceList
})
const ast = babel.transformFromAst(transformResult.ast, '', {
plugins: [
[require('babel-plugin-transform-define').default, constantsReplaceList]
]
}).ast
fileContent = parseAst(ast, filePath, files, isProduction, npmConfig, buildAdapter, compileInclude)
} catch (err) {
console.log(err)
}
if (!copyedFiles[outputNpmPath]) {
if (compileInclude && compileInclude.length) {
const filePathArr = filePath.split(path.sep)
const nodeModulesIndex = filePathArr.indexOf('node_modules')
const npmPkgName = filePathArr[nodeModulesIndex + 1]
if (compileInclude.indexOf(npmPkgName) >= 0) {
const compileScriptRes = await npmProcess.callPlugin('babel', fileContent, filePath, babelConfig)
fileContent = compileScriptRes.code
}
}
if (isProduction) {
const uglifyPluginConfig = pluginsConfig.uglify || { enable: true }
if (uglifyPluginConfig.enable) {
const uglifyConfig = Object.assign(defaultUglifyConfig, uglifyPluginConfig.config || {})
const uglifyResult = npmProcess.callPluginSync('uglifyjs', fileContent, outputNpmPath, uglifyConfig)
if (uglifyResult.error) {
printLog(pocessTypeEnum.ERROR, '压缩错误', `文件${filePath}`)
console.log(uglifyResult.error)
} else {
fileContent = uglifyResult.code
}
}
}
fs.ensureDirSync(path.dirname(outputNpmPath))
fs.writeFileSync(outputNpmPath, fileContent)
let modifyOutput = outputNpmPath.replace(basedir + path.sep, '')
modifyOutput = modifyOutput.split(path.sep).join('/')
printLog(pocessTypeEnum.COPY, 'NPM文件', modifyOutput)
copyedFiles[outputNpmPath] = true
}
}
function npmCodeHack (filePath, content, buildAdapter) {
const basename = path.basename(filePath)
switch (basename) {
case 'lodash.js':
case '_global.js':
case 'lodash.min.js':
if (buildAdapter === BUILD_TYPES.ALIPAY || buildAdapter === BUILD_TYPES.SWAN) {
content = content.replace(/Function\(['"]return this['"]\)\(\)/, '{}')
} else {
content = content.replace(/Function\(['"]return this['"]\)\(\)/, 'this')
}
break
case 'mobx.js':
// 解决支付宝小程序全局window或global不存在的问题
content = content.replace(
/typeof window\s{0,}!==\s{0,}['"]undefined['"]\s{0,}\?\s{0,}window\s{0,}:\s{0,}global/,
'typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : {}'
)
break
case '_html.js':
content = 'module.exports = false;'
break
case '_microtask.js':
content = content.replace('if(Observer)', 'if(false && Observer)')
// IOS 1.10.2 Promise BUG
content = content.replace('Promise && Promise.resolve', 'false && Promise && Promise.resolve')
break
case '_freeGlobal.js':
content = content.replace('module.exports = freeGlobal;', 'module.exports = freeGlobal || this || global || {};')
break
}
if (buildAdapter === BUILD_TYPES.ALIPAY && content.replace(/\s\r\n/g, '').length <= 0) {
content = '// Empty file'
}
return content
}
function getResolvedCache () {
return resolvedCache
}
module.exports = {
getResolvedCache,
resolveNpmFilesPath,
resolveNpmPkgMainPath
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,69 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var Adapters;
(function (Adapters) {
Adapters["weapp"] = "weapp";
Adapters["swan"] = "swan";
Adapters["alipay"] = "alipay";
Adapters["quickapp"] = "quickapp";
Adapters["tt"] = "tt";
})(Adapters = exports.Adapters || (exports.Adapters = {}));
const weixinAdapter = {
if: 'wx:if',
else: 'wx:else',
elseif: 'wx:elif',
for: 'wx:for',
forItem: 'wx:for-item',
forIndex: 'wx:for-index',
key: 'wx:key',
type: "weapp" /* weapp */
};
const swanAdapter = {
if: 's-if',
else: 's-else',
elseif: 's-elif',
for: 's-for',
forItem: 's-for-item',
forIndex: 's-for-index',
key: 's-key',
type: "swan" /* swan */
};
const alipayAdapter = {
if: 'a:if',
else: 'a:else',
elseif: 'a:elif',
for: 'a:for',
forItem: 'a:for-item',
forIndex: 'a:for-index',
key: 'a:key',
type: "alipay" /* alipay */
};
const ttAdapter = {
if: 'tt:if',
else: 'tt:else',
elseif: 'tt:elif',
for: 'tt:for',
forItem: 'tt:for-item',
forIndex: 'tt:for-index',
key: 'tt:key',
type: "tt" /* tt */
};
exports.Adapter = weixinAdapter;
function setAdapter(adapter) {
switch (adapter.toLowerCase()) {
case "swan" /* swan */:
exports.Adapter = swanAdapter;
break;
case "alipay" /* alipay */:
exports.Adapter = alipayAdapter;
break;
case "tt" /* tt */:
exports.Adapter = ttAdapter;
break;
default:
exports.Adapter = weixinAdapter;
break;
}
}
exports.setAdapter = setAdapter;
//# sourceMappingURL=adapter.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"adapter.js","sourceRoot":"","sources":["../../src/adapter.ts"],"names":[],"mappings":";;AAAA,IAAkB,QAMjB;AAND,WAAkB,QAAQ;IACxB,2BAAe,CAAA;IACf,yBAAa,CAAA;IACb,6BAAiB,CAAA;IACjB,iCAAqB,CAAA;IACrB,qBAAS,CAAA;AACX,CAAC,EANiB,QAAQ,GAAR,gBAAQ,KAAR,gBAAQ,QAMzB;AAaD,MAAM,aAAa,GAAY;IAC7B,EAAE,EAAE,OAAO;IACX,IAAI,EAAE,SAAS;IACf,MAAM,EAAE,SAAS;IACjB,GAAG,EAAE,QAAQ;IACb,OAAO,EAAE,aAAa;IACtB,QAAQ,EAAE,cAAc;IACxB,GAAG,EAAE,QAAQ;IACb,IAAI,qBAAgB;CACrB,CAAA;AAED,MAAM,WAAW,GAAY;IAC3B,EAAE,EAAE,MAAM;IACV,IAAI,EAAE,QAAQ;IACd,MAAM,EAAE,QAAQ;IAChB,GAAG,EAAE,OAAO;IACZ,OAAO,EAAE,YAAY;IACrB,QAAQ,EAAE,aAAa;IACvB,GAAG,EAAE,OAAO;IACZ,IAAI,mBAAe;CACpB,CAAA;AAED,MAAM,aAAa,GAAY;IAC7B,EAAE,EAAE,MAAM;IACV,IAAI,EAAE,QAAQ;IACd,MAAM,EAAE,QAAQ;IAChB,GAAG,EAAE,OAAO;IACZ,OAAO,EAAE,YAAY;IACrB,QAAQ,EAAE,aAAa;IACvB,GAAG,EAAE,OAAO;IACZ,IAAI,uBAAiB;CACtB,CAAA;AAED,MAAM,SAAS,GAAY;IACzB,EAAE,EAAE,OAAO;IACX,IAAI,EAAE,SAAS;IACf,MAAM,EAAE,SAAS;IACjB,GAAG,EAAE,QAAQ;IACb,OAAO,EAAE,aAAa;IACtB,QAAQ,EAAE,cAAc;IACxB,GAAG,EAAE,QAAQ;IACb,IAAI,eAAa;CAClB,CAAA;AAEU,QAAA,OAAO,GAAY,aAAa,CAAA;AAE3C,SAAgB,UAAU,CAAE,OAAiB;IAC3C,QAAQ,OAAO,CAAC,WAAW,EAAE,EAAE;QAC7B;YACE,eAAO,GAAG,WAAW,CAAA;YACrB,MAAK;QACP;YACE,eAAO,GAAG,aAAa,CAAA;YACvB,MAAK;QACP;YACE,eAAO,GAAG,SAAS,CAAA;YACnB,MAAK;QACP;YACE,eAAO,GAAG,aAAa,CAAA;YACvB,MAAK;KACR;AACH,CAAC;AAfD,gCAeC"}

View File

@ -0,0 +1,641 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const t = require("babel-types");
const utils_1 = require("./utils");
const constant_1 = require("./constant");
const lodash_1 = require("lodash");
const render_1 = require("./render");
const jsx_1 = require("./jsx");
const adapter_1 = require("./adapter");
const babel_generator_1 = require("babel-generator");
function buildConstructor() {
const ctor = t.classMethod('constructor', t.identifier('constructor'), [t.identifier('props')], t.blockStatement([
t.expressionStatement(t.callExpression(t.identifier('super'), [
t.identifier('props')
]))
]));
return ctor;
}
function processThisPropsFnMemberProperties(member, path, args, binded) {
const propertyArray = [];
function traverseMember(member) {
const object = member.object;
const property = member.property;
if (t.isIdentifier(property)) {
propertyArray.push(property.name);
}
if (t.isMemberExpression(object)) {
if (t.isThisExpression(object.object) &&
t.isIdentifier(object.property) &&
object.property.name === 'props') {
if ("alipay" /* alipay */ === adapter_1.Adapter.type) {
if (binded)
args.shift();
path.replaceWith(t.callExpression(t.memberExpression(t.thisExpression(), t.identifier('__triggerPropsFn')), [
t.stringLiteral(propertyArray.reverse().join('.')),
t.arrayExpression(args)
]));
}
else {
path.replaceWith(t.callExpression(t.memberExpression(t.thisExpression(), t.identifier('__triggerPropsFn')), [t.stringLiteral(propertyArray.reverse().join('.')), t.callExpression(t.memberExpression(t.arrayExpression([t.nullLiteral()]), t.identifier('concat')), [t.arrayExpression(args)])]));
}
}
traverseMember(object);
}
}
traverseMember(member);
}
class Transformer {
constructor(path, sourcePath, componentProperies,
//@fix add arg => componentSourceMap
componentSourceMap) {
this.result = {
template: '',
components: [],
componentProperies: []
};
this.methods = new Map();
this.initState = new Set();
this.jsxReferencedIdentifiers = new Set();
this.customComponents = new Map();
this.anonymousMethod = new Map();
this.renderMethod = null;
this.customComponentNames = new Set();
this.usedState = new Set();
this.loopStateName = new Map();
this.customComponentData = [];
this.refs = [];
this.loopRefs = new Map();
this.anonymousFuncCounter = utils_1.incrementId();
this.buildPropsAnonymousFunc = (attr, expr, isBind = false) => {
const { code } = babel_generator_1.default(expr);
if (code.startsWith('this.props')) {
const methodName = utils_1.findMethodName(expr);
const hasMethodName = this.anonymousMethod.has(methodName) || !methodName;
const funcName = hasMethodName
? this.anonymousMethod.get(methodName)
// 测试时使用1个稳定的 uniqueID 便于测试实际使用5个英文字母否则小程序不支持
: process.env.NODE_ENV === 'test' ? lodash_1.uniqueId('funPrivate') : `funPrivate${utils_1.createRandomLetters(5)}`;
this.anonymousMethod.set(methodName, funcName);
const newVal = isBind
? t.callExpression(t.memberExpression(t.memberExpression(t.thisExpression(), t.identifier(funcName)), t.identifier('bind')), expr.arguments || [])
: t.memberExpression(t.thisExpression(), t.identifier(funcName));
attr.get('value.expression').replaceWith(newVal);
this.methods.set(funcName, null);
this.componentProperies.add(methodName);
if (hasMethodName) {
return;
}
const attrName = attr.node.name;
if (t.isJSXIdentifier(attrName) && attrName.name.startsWith('on')) {
this.componentProperies.add(`__fn_${attrName.name}`);
}
if (methodName.startsWith('on')) {
this.componentProperies.add(`__fn_${methodName}`);
}
const method = t.classMethod('method', t.identifier(funcName), [], t.blockStatement([
t.expressionStatement(t.callExpression(t.memberExpression(t.thisExpression(), t.identifier('__triggerPropsFn')), [t.stringLiteral(methodName), t.arrayExpression([t.spreadElement(t.identifier('arguments'))])]))
]));
this.classPath.node.body.body = this.classPath.node.body.body.concat(method);
}
};
this.classPath = path;
this.sourcePath = sourcePath;
//@fix add arg => componentSourceMap
this.componentSourceMap = componentSourceMap;
for (let key of this.componentSourceMap) {
const arr = key[0].split('/');
if (arr[arr.length - 1].indexOf('-') !== -1 && key[0].indexOf('components/') !== -1 && key[1].length === 0) {
this.customComponents.set(arr[arr.length - 1], {
sourcePath: key[0],
type: 'default'
});
}
}
this.moduleNames = Object.keys(path.scope.getAllBindings('module'));
this.componentProperies = new Set(componentProperies);
this.compile();
}
setMultipleSlots() {
const body = this.classPath.node.body.body;
if (body.some(c => t.isClassProperty(c) && c.key.name === 'multipleSlots')) {
return;
}
const multipleSlots = t.classProperty(t.identifier('multipleSlots'), t.booleanLiteral(true));
multipleSlots.static = true;
body.push(multipleSlots);
}
createStringRef(componentName, id, refName) {
this.refs.push({
type: constant_1.DEFAULT_Component_SET.has(componentName) ? 'dom' : 'component',
id,
refName
});
}
createFunctionRef(componentName, id, fn) {
this.refs.push({
type: constant_1.DEFAULT_Component_SET.has(componentName) ? 'dom' : 'component',
id,
fn
});
}
handleRefs() {
const objExpr = this.refs.map(ref => {
return t.objectExpression([
t.objectProperty(t.identifier('type'), t.stringLiteral(ref.type)),
t.objectProperty(t.identifier('id'), t.stringLiteral(ref.id)),
t.objectProperty(t.identifier('refName'), t.stringLiteral(ref.refName || '')),
t.objectProperty(t.identifier('fn'), ref.fn ? ref.fn : t.nullLiteral())
]);
});
this.classPath.node.body.body.push(t.classProperty(t.identifier('$$refs'), t.arrayExpression(objExpr)));
}
traverse() {
const self = this;
self.classPath.traverse({
JSXOpeningElement: (path) => {
const jsx = path.node;
const attrs = jsx.attributes;
if (!t.isJSXIdentifier(jsx.name)) {
return;
}
const loopCallExpr = path.findParent(p => utils_1.isArrayMapCallExpression(p));
const componentName = jsx.name.name;
const refAttr = jsx_1.findJSXAttrByName(attrs, 'ref');
if (!refAttr) {
return;
}
const idAttr = jsx_1.findJSXAttrByName(attrs, 'id');
let id = utils_1.createRandomLetters(5);
let idExpr;
if (!idAttr) {
if (loopCallExpr && loopCallExpr.isCallExpression()) {
const [func] = loopCallExpr.node.arguments;
let indexId = null;
if (t.isFunctionExpression(func) || t.isArrowFunctionExpression(func)) {
const params = func.params;
indexId = params[1];
}
if (indexId === null || !t.isIdentifier(indexId)) {
throw utils_1.codeFrameError(path.node, '在循环中使用 ref 必须暴露循环的第二个参数 `index`');
}
attrs.push(t.jSXAttribute(t.jSXIdentifier('id'), t.jSXExpressionContainer(t.binaryExpression('+', t.stringLiteral(id), indexId))));
}
else {
attrs.push(t.jSXAttribute(t.jSXIdentifier('id'), t.stringLiteral(id)));
}
}
else {
const idValue = idAttr.value;
if (t.isStringLiteral(idValue)) {
id = idValue.value;
}
else if (t.isJSXExpressionContainer(idValue)) {
if (t.isStringLiteral(idValue.expression)) {
id = idValue.expression.value;
}
else {
idExpr = idValue.expression;
}
}
}
if (t.isStringLiteral(refAttr.value)) {
if (loopCallExpr) {
throw utils_1.codeFrameError(refAttr, '循环中的 ref 只能使用函数。');
}
this.createStringRef(componentName, id, refAttr.value.value);
}
if (t.isJSXExpressionContainer(refAttr.value)) {
const expr = refAttr.value.expression;
if (t.isStringLiteral(expr)) {
if (loopCallExpr) {
throw utils_1.codeFrameError(refAttr, '循环中的 ref 只能使用函数。');
}
this.createStringRef(componentName, id, expr.value);
}
else if (t.isArrowFunctionExpression(expr) || t.isMemberExpression(expr)) {
const type = constant_1.DEFAULT_Component_SET.has(componentName) ? 'dom' : 'component';
if (loopCallExpr) {
this.loopRefs.set(path.parentPath.node, {
id: idExpr || id,
fn: expr,
type,
component: path.parentPath
});
}
else {
this.refs.push({
type,
id,
fn: expr
});
}
}
else {
throw utils_1.codeFrameError(refAttr, 'ref 仅支持传入字符串、匿名箭头函数和 class 中已声明的函数');
}
}
for (const [index, attr] of attrs.entries()) {
if (attr === refAttr) {
attrs.splice(index, 1);
}
}
},
ClassMethod(path) {
const node = path.node;
if (t.isIdentifier(node.key)) {
const name = node.key.name;
self.methods.set(name, path);
if (name === 'render') {
self.renderMethod = path;
path.traverse({
ReturnStatement(returnPath) {
const arg = returnPath.node.argument;
const ifStem = returnPath.findParent(p => p.isIfStatement());
if (ifStem && ifStem.isIfStatement() && arg === null) {
const consequent = ifStem.get('consequent');
if (consequent.isBlockStatement() && consequent.node.body.includes(returnPath.node)) {
returnPath.get('argument').replaceWith(t.nullLiteral());
}
}
}
});
}
if (name === 'constructor') {
path.traverse({
AssignmentExpression(p) {
if (t.isMemberExpression(p.node.left) &&
t.isThisExpression(p.node.left.object) &&
t.isIdentifier(p.node.left.property) &&
p.node.left.property.name === 'data' &&
t.isObjectExpression(p.node.right)) {
const properties = p.node.right.properties;
properties.forEach(p => {
if (t.isObjectProperty(p) && t.isIdentifier(p.key)) {
self.initState.add(p.key.name);
}
});
}
}
});
}
}
},
IfStatement(path) {
const test = path.get('test');
const consequent = path.get('consequent');
if (utils_1.isContainJSXElement(consequent) && utils_1.hasComplexExpression(test)) {
const scope = self.renderMethod && self.renderMethod.scope || path.scope;
utils_1.generateAnonymousState(scope, test, self.jsxReferencedIdentifiers, true);
}
},
ClassProperty(path) {
const { key: { name }, value } = path.node;
if (t.isArrowFunctionExpression(value) || t.isFunctionExpression(value)) {
self.methods.set(name, path);
}
if (name === 'data' && t.isObjectExpression(value)) {
value.properties.forEach(p => {
if (t.isObjectProperty(p)) {
if (t.isIdentifier(p.key)) {
self.initState.add(p.key.name);
}
}
});
}
},
JSXExpressionContainer(path) {
const attr = path.findParent(p => p.isJSXAttribute());
const isFunctionProp = attr && typeof attr.node.name.name === 'string' && attr.node.name.name.startsWith('on');
path.traverse({
MemberExpression(path) {
const sibling = path.getSibling('property');
if (path.get('object').isThisExpression() &&
(path.get('property').isIdentifier({ name: 'props' }) || path.get('property').isIdentifier({ name: 'data' })) &&
sibling.isIdentifier()) {
if (!isFunctionProp) {
self.usedState.add(sibling.node.name);
}
}
}
});
const expression = path.get('expression');
const scope = self.renderMethod && self.renderMethod.scope || path.scope;
const calleeExpr = expression.get('callee');
const parentPath = path.parentPath;
if (utils_1.hasComplexExpression(expression) &&
!isFunctionProp &&
!(calleeExpr &&
calleeExpr.isMemberExpression() &&
calleeExpr.get('object').isMemberExpression() &&
calleeExpr.get('property').isIdentifier({ name: 'bind' })) // is not bind
) {
utils_1.generateAnonymousState(scope, expression, self.jsxReferencedIdentifiers);
}
else {
if (parentPath.isJSXAttribute()) {
if (!(expression.isMemberExpression() || expression.isIdentifier()) && parentPath.node.name.name === 'key') {
utils_1.generateAnonymousState(scope, expression, self.jsxReferencedIdentifiers);
}
}
}
if (!attr)
return;
const key = attr.node.name;
const value = attr.node.value;
if (!t.isJSXIdentifier(key)) {
return;
}
if (t.isJSXIdentifier(key) && key.name.startsWith('on') && t.isJSXExpressionContainer(value)) {
const expr = value.expression;
if (t.isCallExpression(expr) && t.isMemberExpression(expr.callee) && t.isIdentifier(expr.callee.property, { name: 'bind' })) {
self.buildPropsAnonymousFunc(attr, expr, true);
}
else if (t.isMemberExpression(expr)) {
self.buildPropsAnonymousFunc(attr, expr, false);
}
else if (t.isArrowFunctionExpression(expr)) {
const exprPath = attr.get('value.expression');
const stemParent = path.getStatementParent();
const counter = self.anonymousFuncCounter();
const anonymousFuncName = `anonymousFunc${counter}`;
const isCatch = utils_1.isContainStopPropagation(exprPath);
const classBody = self.classPath.node.body.body;
const loopCallExpr = path.findParent(p => utils_1.isArrayMapCallExpression(p));
let index;
if (loopCallExpr) {
index = lodash_1.get(loopCallExpr, 'node.arguments[0].params[1]');
if (!t.isIdentifier(index)) {
index = t.identifier('__index' + counter);
lodash_1.set(loopCallExpr, 'node.arguments[0].params[1]', index);
}
classBody.push(t.classProperty(t.identifier(anonymousFuncName + 'Array'), t.arrayExpression([])));
const arrayFunc = t.memberExpression(t.memberExpression(t.thisExpression(), t.identifier(anonymousFuncName + 'Array')), t.identifier(index.name), true);
classBody.push(t.classMethod('method', t.identifier(anonymousFuncName), [t.identifier(index.name), t.identifier('e')], t.blockStatement([
isCatch ? t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('e'), t.identifier('stopPropagation')), [])) : t.emptyStatement(),
t.expressionStatement(t.logicalExpression('&&', arrayFunc, t.callExpression(arrayFunc, [t.identifier('e')])))
])));
exprPath.replaceWith(t.callExpression(t.memberExpression(t.memberExpression(t.thisExpression(), t.identifier(anonymousFuncName)), t.identifier('bind')), [t.thisExpression(), t.identifier(index.name)]));
stemParent.insertBefore(t.expressionStatement(t.assignmentExpression('=', arrayFunc, expr)));
}
else {
classBody.push(t.classMethod('method', t.identifier(anonymousFuncName), [t.identifier('e')], t.blockStatement([
isCatch ? t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('e'), t.identifier('stopPropagation')), [])) : t.emptyStatement()
])));
exprPath.replaceWith(t.memberExpression(t.thisExpression(), t.identifier(anonymousFuncName)));
stemParent.insertBefore(t.expressionStatement(t.assignmentExpression('=', t.memberExpression(t.thisExpression(), t.identifier(anonymousFuncName)), expr)));
}
}
else {
throw utils_1.codeFrameError(path.node, '组件事件传参只能在使用匿名箭头函数,或使用类作用域下的确切引用(this.handleXX || this.props.handleXX),或使用 bind。');
}
}
const jsx = path.findParent(p => p.isJSXOpeningElement());
if (!jsx)
return;
const jsxName = jsx.node.name;
if (!t.isJSXIdentifier(jsxName))
return;
if (expression.isJSXElement())
return;
if (constant_1.DEFAULT_Component_SET.has(jsxName.name) || expression.isIdentifier() || expression.isMemberExpression() || expression.isLiteral() || expression.isLogicalExpression() || expression.isConditionalExpression() || key.name.startsWith('on') || expression.isCallExpression())
return;
utils_1.generateAnonymousState(scope, expression, self.jsxReferencedIdentifiers);
},
JSXElement(path) {
const id = path.node.openingElement.name;
//@fix extrat xx-xx components to customComponents list
if (t.isJSXIdentifier(id) && id.name.indexOf('-') !== -1) {
for (let key of self.componentSourceMap) {
const arr = key[0].split('/');
if (arr[arr.length - 1] === id.name && key[1].length === 0) {
self.customComponents.set(id.name, {
sourcePath: key[0],
type: 'default'
});
}
}
}
if (t.isJSXIdentifier(id) &&
!constant_1.DEFAULT_Component_SET.has(id.name) &&
self.moduleNames.indexOf(id.name) !== -1) {
const name = id.name;
const binding = self.classPath.scope.getBinding(name);
if (binding && t.isImportDeclaration(binding.path.parent)) {
const sourcePath = binding.path.parent.source.value;
if (binding.path.isImportDefaultSpecifier()) {
self.customComponents.set(name, {
sourcePath,
type: 'default'
});
}
else {
self.customComponents.set(name, {
sourcePath,
type: 'pattern'
});
}
}
}
},
MemberExpression: (path) => {
const object = path.get('object');
const property = path.get('property');
if (!(object.isThisExpression() && property.isIdentifier({ name: 'props' }))) {
return;
}
const parentPath = path.parentPath;
if (parentPath.isMemberExpression()) {
const siblingProp = parentPath.get('property');
if (siblingProp.isIdentifier()) {
const name = siblingProp.node.name;
if (name === 'children') {
parentPath.replaceWith(t.jSXElement(t.jSXOpeningElement(t.jSXIdentifier('slot'), [], true), t.jSXClosingElement(t.jSXIdentifier('slot')), [], true));
}
else if (/^render[A-Z]/.test(name)) {
const slotName = utils_1.getSlotName(name);
parentPath.replaceWith(t.jSXElement(t.jSXOpeningElement(t.jSXIdentifier('slot'), [
t.jSXAttribute(t.jSXIdentifier('name'), t.stringLiteral(slotName))
], true), t.jSXClosingElement(t.jSXIdentifier('slot')), []));
this.setMultipleSlots();
}
else {
self.componentProperies.add(siblingProp.node.name);
}
}
}
else if (parentPath.isVariableDeclarator()) {
const siblingId = parentPath.get('id');
if (siblingId.isObjectPattern()) {
const properties = siblingId.node.properties;
for (const prop of properties) {
if (t.isRestProperty(prop)) {
throw utils_1.codeFrameError(prop.loc, 'this.props 不支持使用 rest property 语法,请把每一个 prop 都单独列出来');
}
else if (t.isIdentifier(prop.key)) {
self.componentProperies.add(prop.key.name);
}
}
}
}
},
CallExpression(path) {
const node = path.node;
const callee = node.callee;
if (t.isMemberExpression(callee) && t.isMemberExpression(callee.object)) {
const property = callee.property;
if (t.isIdentifier(property)) {
if (property.name.startsWith('on')) {
self.componentProperies.add(`__fn_${property.name}`);
processThisPropsFnMemberProperties(callee, path, node.arguments, false);
}
else if (property.name === 'call' || property.name === 'apply') {
self.componentProperies.add(`__fn_${property.name}`);
processThisPropsFnMemberProperties(callee.object, path, node.arguments, true);
}
}
}
}
});
}
setComponents() {
this.customComponents.forEach((component, name) => {
this.result.components.push({
path: utils_1.pathResolver(component.sourcePath, this.sourcePath),
name: lodash_1.kebabCase(name),
type: component.type
});
});
}
setMethods() {
const methods = this.classPath.get('body').get('body');
for (const method of methods) {
if (method.isClassMethod()) {
const key = method.get('key');
if (key.isIdentifier()) {
this.methods.set(key.node.name, method);
}
}
}
}
resetConstructor() {
const body = this.classPath.node.body.body;
if (!this.methods.has('constructor')) {
const ctor = buildConstructor();
body.unshift(ctor);
}
if (process.env.NODE_ENV === 'test') {
return;
}
for (const method of body) {
if (t.isClassMethod(method) && method.kind === 'constructor') {
method.kind = 'method';
method.key = t.identifier('_constructor');
if (t.isBlockStatement(method.body)) {
for (const statement of method.body.body) {
if (t.isExpressionStatement(statement)) {
const expr = statement.expression;
if (t.isCallExpression(expr) && (t.isIdentifier(expr.callee, { name: 'super' }) || t.isSuper(expr.callee))) {
expr.callee = t.memberExpression(t.identifier('super'), t.identifier('_constructor'));
}
}
}
}
}
}
}
handleLifecyclePropParam(propParam, properties) {
let propsName = null;
if (!propParam) {
return null;
}
if (t.isIdentifier(propParam)) {
propsName = propParam.name;
}
else if (t.isObjectPattern(propParam)) {
for (const prop of propParam.properties) {
if (t.isObjectProperty(prop) && t.isIdentifier(prop.key)) {
properties.add(prop.key.name);
}
else if (t.isRestProperty(prop) && t.isIdentifier(prop.argument)) {
propsName = prop.argument.name;
}
}
}
else {
throw utils_1.codeFrameError(propParam.loc, '此生命周期的第一个参数只支持写标识符或对象解构');
}
return propsName;
}
findMoreProps() {
// 第一个参数是 props 的生命周期
const lifeCycles = new Set([
// 'constructor',
'componentDidUpdate',
'shouldComponentUpdate',
'getDerivedStateFromProps',
'getSnapshotBeforeUpdate',
'componentWillReceiveProps',
'componentWillUpdate'
]);
const properties = new Set();
this.methods.forEach((method, name) => {
if (!lifeCycles.has(name)) {
return;
}
const node = method.node;
let propsName = null;
if (t.isClassMethod(node)) {
propsName = this.handleLifecyclePropParam(node.params[0], properties);
}
else if (t.isArrowFunctionExpression(node.value) || t.isFunctionExpression(node.value)) {
propsName = this.handleLifecyclePropParam(node.value.params[0], properties);
}
if (propsName === null) {
return;
}
method.traverse({
MemberExpression(path) {
if (!path.isReferencedMemberExpression()) {
return;
}
const { object, property } = path.node;
if (t.isIdentifier(object, { name: propsName }) && t.isIdentifier(property)) {
properties.add(property.name);
}
},
VariableDeclarator(path) {
const { id, init } = path.node;
if (t.isObjectPattern(id) && t.isIdentifier(init, { name: propsName })) {
for (const prop of id.properties) {
if (t.isObjectProperty(prop) && t.isIdentifier(prop.key)) {
properties.add(prop.key.name);
}
}
}
}
});
properties.forEach((value) => {
this.componentProperies.add(value);
});
});
}
parseRender() {
if (this.renderMethod) {
this.result.template = this.result.template
+ new render_1.RenderParser(this.renderMethod, this.methods, this.initState, this.jsxReferencedIdentifiers, this.usedState, this.loopStateName, this.customComponentNames, this.customComponentData, this.componentProperies, this.loopRefs).outputTemplate;
}
}
compile() {
this.traverse();
this.setMethods();
this.setComponents();
this.resetConstructor();
this.findMoreProps();
this.handleRefs();
this.parseRender();
this.result.componentProperies = [...this.componentProperies];
}
}
exports.Transformer = Transformer;
//# sourceMappingURL=class.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,94 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.THIRD_PARTY_COMPONENTS = new Set();
// tslint:disable-next-line:variable-name
exports.DEFAULT_Component_SET = new Set([
'View',
'ScrollView',
'Swiper',
'MovableView',
'CoverView',
'CoverImage',
'Icon',
'Text',
'RichText',
'Progress',
'Button',
'Checkbox',
'Form',
'Input',
'Label',
'Picker',
'PickerView',
'PickerViewColumn',
'Radio',
'RadioGroup',
'CheckboxGroup',
'Slider',
'Switch',
'Textarea',
'Navigator',
'Audio',
'Image',
'Video',
'Camera',
'LivePlayer',
'LivePusher',
'Map',
'Canvas',
'OpenData',
'WebView',
'SwiperItem',
'MovableArea',
'MovableView',
'FunctionalPageNavigator',
'Ad',
'Block',
'Import',
'OfficialAccount'
]);
exports.INTERNAL_SAFE_GET = 'internal_safe_get';
exports.TARO_PACKAGE_NAME = '@tarojs/taro';
exports.COMPONENTS_PACKAGE_NAME = '@tarojs/components';
exports.REDUX_PACKAGE_NAME = '@tarojs/redux';
exports.MOBX_PACKAGE_NAME = '@tarojs/mobx';
exports.MAP_CALL_ITERATOR = '__item';
exports.INTERNAL_INLINE_STYLE = 'internal_inline_style';
exports.INTERNAL_GET_ORIGNAL = 'internal_get_original';
exports.GEL_ELEMENT_BY_ID = 'getElementById';
exports.LOOP_STATE = '$loopState';
exports.LOOP_ORIGINAL = '$original';
exports.setLoopOriginal = (s) => exports.LOOP_ORIGINAL = s;
exports.LOOP_CALLEE = '$anonymousCallee_';
exports.SPECIAL_COMPONENT_PROPS = new Map();
exports.SPECIAL_COMPONENT_PROPS.set('Progress', new Set([
'activeColor',
'backgroundColor'
]));
exports.IMAGE_COMPONENTS = new Set([
'Image',
'CoverImage'
]);
exports.swanSpecialAttrs = {
'ScrollView': ['scrollTop', 'scrollLeft', 'scrollIntoView'],
'Input': ['value'],
'Textarea': ['value'],
'MovableView': ['x', 'y'],
'Slider': ['value']
};
exports.ALIPAY_BUBBLE_EVENTS = new Set([
'onTouchStart',
'onTouchMove',
'onTouchEnd',
'onTouchCancel',
'onClick',
'onLongTap'
]);
exports.TRANSFORM_COMPONENT_PROPS = new Map();
exports.TRANSFORM_COMPONENT_PROPS.set("alipay" /* alipay */, {
'Canvas': {
'canvasId': 'id'
}
});
exports.lessThanSignPlacehold = '__LESS_THAN_SIGN_PLACEHOLDER__';
//# sourceMappingURL=constant.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"constant.js","sourceRoot":"","sources":["../../src/constant.ts"],"names":[],"mappings":";;AAEa,QAAA,sBAAsB,GAAG,IAAI,GAAG,EAAU,CAAA;AAEvD,yCAAyC;AAC5B,QAAA,qBAAqB,GAAG,IAAI,GAAG,CAAS;IACnD,MAAM;IACN,YAAY;IACZ,QAAQ;IACR,aAAa;IACb,WAAW;IACX,YAAY;IACZ,MAAM;IACN,MAAM;IACN,UAAU;IACV,UAAU;IACV,QAAQ;IACR,UAAU;IACV,MAAM;IACN,OAAO;IACP,OAAO;IACP,QAAQ;IACR,YAAY;IACZ,kBAAkB;IAClB,OAAO;IACP,YAAY;IACZ,eAAe;IACf,QAAQ;IACR,QAAQ;IACR,UAAU;IACV,WAAW;IACX,OAAO;IACP,OAAO;IACP,OAAO;IACP,QAAQ;IACR,YAAY;IACZ,YAAY;IACZ,KAAK;IACL,QAAQ;IACR,UAAU;IACV,SAAS;IACT,YAAY;IACZ,aAAa;IACb,aAAa;IACb,yBAAyB;IACzB,IAAI;IACJ,OAAO;IACP,QAAQ;IACR,iBAAiB;CAClB,CAAC,CAAA;AAEW,QAAA,iBAAiB,GAAG,mBAAmB,CAAA;AAEvC,QAAA,iBAAiB,GAAG,cAAc,CAAA;AAElC,QAAA,uBAAuB,GAAG,oBAAoB,CAAA;AAE9C,QAAA,kBAAkB,GAAG,eAAe,CAAA;AAEpC,QAAA,iBAAiB,GAAG,cAAc,CAAA;AAElC,QAAA,iBAAiB,GAAG,QAAQ,CAAA;AAE5B,QAAA,qBAAqB,GAAG,uBAAuB,CAAA;AAE/C,QAAA,oBAAoB,GAAG,uBAAuB,CAAA;AAE9C,QAAA,iBAAiB,GAAG,gBAAgB,CAAA;AAEpC,QAAA,UAAU,GAAG,YAAY,CAAA;AAE3B,QAAA,aAAa,GAAG,WAAW,CAAA;AAEzB,QAAA,eAAe,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,qBAAa,GAAG,CAAC,CAAA;AAElD,QAAA,WAAW,GAAG,mBAAmB,CAAA;AAEjC,QAAA,uBAAuB,GAAG,IAAI,GAAG,EAAuB,CAAA;AAErE,+BAAuB,CAAC,GAAG,CACzB,UAAU,EACV,IAAI,GAAG,CAAC;IACN,aAAa;IACb,iBAAiB;CAClB,CAAC,CACH,CAAA;AAEY,QAAA,gBAAgB,GAAG,IAAI,GAAG,CAAS;IAC9C,OAAO;IACP,YAAY;CACb,CAAC,CAAA;AAEW,QAAA,gBAAgB,GAAG;IAC9B,YAAY,EAAE,CAAC,WAAW,EAAE,YAAY,EAAE,gBAAgB,CAAC;IAC3D,OAAO,EAAE,CAAC,OAAO,CAAC;IAClB,UAAU,EAAE,CAAC,OAAO,CAAC;IACrB,aAAa,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC;IACzB,QAAQ,EAAE,CAAC,OAAO,CAAC;CACpB,CAAA;AAEY,QAAA,oBAAoB,GAAG,IAAI,GAAG,CAAS;IAClD,cAAc;IACd,aAAa;IACb,YAAY;IACZ,eAAe;IACf,SAAS;IACT,WAAW;CACZ,CAAC,CAAA;AAEW,QAAA,yBAAyB,GAAG,IAAI,GAAG,EAA0D,CAAA;AAE1G,iCAAyB,CAAC,GAAG,wBAAkB;IAC7C,QAAQ,EAAE;QACR,UAAU,EAAE,IAAI;KACjB;CACF,CAAC,CAAA;AAEW,QAAA,qBAAqB,GAAG,gCAAgC,CAAA"}

View File

@ -0,0 +1,43 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const voidHtmlTags = new Set([
// 'image',
'img',
'input',
'import'
]);
if (process.env.NODE_ENV === 'test') {
voidHtmlTags.add('image');
}
function stringifyAttributes(input) {
const attributes = [];
for (const key of Object.keys(input)) {
let value = input[key];
if (value === false) {
continue;
}
if (Array.isArray(value)) {
value = value.join(' ');
}
let attribute = key;
if (value !== true) {
attribute += `="${String(value)}"`;
}
attributes.push(attribute);
}
return attributes.length > 0 ? ' ' + attributes.join(' ') : '';
}
exports.createHTMLElement = (options) => {
options = Object.assign({
name: 'div',
attributes: {},
value: ''
}, options);
const isVoidTag = voidHtmlTags.has(options.name);
let ret = `<${options.name}${stringifyAttributes(options.attributes)}${isVoidTag ? `/` : ''}>`;
if (!isVoidTag) {
ret += `${options.value}</${options.name}>`;
}
return ret;
};
//# sourceMappingURL=create-html-element.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"create-html-element.js","sourceRoot":"","sources":["../../src/create-html-element.ts"],"names":[],"mappings":";;AAAA,MAAM,YAAY,GAAG,IAAI,GAAG,CAAS;IACnC,WAAW;IACX,KAAK;IACL,OAAO;IACP,QAAQ;CACT,CAAC,CAAA;AAEF,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,MAAM,EAAE;IACnC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;CAC1B;AAQD,SAAS,mBAAmB,CAAE,KAAa;IACzC,MAAM,UAAU,GAAa,EAAE,CAAA;IAE/B,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE;QACpC,IAAI,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAA;QAEtB,IAAI,KAAK,KAAK,KAAK,EAAE;YACnB,SAAQ;SACT;QAED,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;YACxB,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;SACxB;QAED,IAAI,SAAS,GAAG,GAAG,CAAA;QAEnB,IAAI,KAAK,KAAK,IAAI,EAAE;YAClB,SAAS,IAAI,KAAK,MAAM,CAAC,KAAK,CAAC,GAAG,CAAA;SACnC;QAED,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;KAC3B;IAED,OAAO,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;AAEhE,CAAC;AAEY,QAAA,iBAAiB,GAAG,CAAC,OAAgB,EAAE,EAAE;IACpD,OAAO,GAAG,MAAM,CAAC,MAAM,CACrB;QACE,IAAI,EAAE,KAAK;QACX,UAAU,EAAE,EAAE;QACd,KAAK,EAAE,EAAE;KACV,EACD,OAAO,CACR,CAAA;IAED,MAAM,SAAS,GAAG,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;IAEhD,IAAI,GAAG,GAAG,IAAI,OAAO,CAAC,IAAI,GAAG,mBAAmB,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAG,GAAG,CAAA;IAE/F,IAAI,CAAC,SAAS,EAAE;QACd,GAAG,IAAI,GAAG,OAAO,CAAC,KAAK,KAAK,OAAO,CAAC,IAAI,GAAG,CAAA;KAC5C;IAED,OAAO,GAAG,CAAA;AACZ,CAAC,CAAA"}

View File

@ -0,0 +1,47 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const eslint_1 = require("eslint");
const utils_1 = require("./utils");
const cli = new eslint_1.CLIEngine({
baseConfig: {
extends: ['plugin:taro/transformer']
},
useEslintrc: false,
parser: 'babel-eslint',
parserOptions: {
ecmaVersion: 2018,
ecmaFeatures: {
jsx: true,
legacyDecorators: true
}
}
});
exports.eslintValidation = () => {
return {
visitor: {
Program(_, state) {
const { file: { code } } = state;
const report = cli.executeOnText(code);
if (report.errorCount > 0) {
for (const result of report.results) {
for (const msg of result.messages) {
const err = utils_1.codeFrameError({
start: {
line: msg.line,
column: msg.column
},
end: {
line: msg.endLine,
column: msg.endColumn
}
}, msg.message);
// tslint:disable-next-line
console.warn('\n' + `ESLint(${msg.ruleId}) 错误:` + err.message + '\n');
}
}
}
}
}
};
};
//# sourceMappingURL=eslint.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"eslint.js","sourceRoot":"","sources":["../../src/eslint.ts"],"names":[],"mappings":";;AAAA,mCAAkC;AAElC,mCAAwC;AAExC,MAAM,GAAG,GAAG,IAAI,kBAAS,CAAC;IACxB,UAAU,EAAE;QACV,OAAO,EAAE,CAAC,yBAAyB,CAAC;KACrC;IACD,WAAW,EAAE,KAAK;IAClB,MAAM,EAAE,cAAc;IACtB,aAAa,EAAE;QACb,WAAW,EAAE,IAAI;QACjB,YAAY,EAAE;YACZ,GAAG,EAAE,IAAI;YACT,gBAAgB,EAAE,IAAI;SACvB;KACF;CACF,CAAC,CAAA;AAEW,QAAA,gBAAgB,GAEzB,GAAG,EAAE;IACP,OAAO;QACL,OAAO,EAAE;YACP,OAAO,CAAE,CAAC,EAAE,KAAK;gBACf,MAAM,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE,GAAG,KAAK,CAAA;gBAChC,MAAM,MAAM,GAAG,GAAG,CAAC,aAAa,CAAC,IAAI,CAAC,CAAA;gBACtC,IAAI,MAAM,CAAC,UAAU,GAAG,CAAC,EAAE;oBACzB,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,OAAO,EAAE;wBACnC,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,QAAQ,EAAE;4BACjC,MAAM,GAAG,GAAG,sBAAc,CAAC;gCACzB,KAAK,EAAE;oCACL,IAAI,EAAE,GAAG,CAAC,IAAI;oCACd,MAAM,EAAE,GAAG,CAAC,MAAM;iCACnB;gCACD,GAAG,EAAE;oCACH,IAAI,EAAE,GAAG,CAAC,OAAO;oCACjB,MAAM,EAAE,GAAG,CAAC,SAAS;iCACtB;6BACF,EAAE,GAAG,CAAC,OAAO,CAAC,CAAA;4BACf,2BAA2B;4BAC3B,OAAO,CAAC,IAAI,CAAC,IAAI,GAAG,UAAU,GAAG,CAAC,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,GAAG,IAAI,CAAC,CAAA;yBACtE;qBACF;iBACF;YACH,CAAC;SACF;KACF,CAAA;AACH,CAAC,CAAA"}

View File

@ -0,0 +1,488 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const babel_traverse_1 = require("babel-traverse");
const babel_generator_1 = require("babel-generator");
const html_1 = require("html");
const babel_core_1 = require("babel-core");
const ts = require("typescript");
const class_1 = require("./class");
const utils_1 = require("./utils");
const t = require("babel-types");
const constant_1 = require("./constant");
const adapter_1 = require("./adapter");
const options_1 = require("./options");
const lodash_1 = require("lodash");
const template = require('babel-template');
function getIdsFromMemberProps(member) {
let ids = [];
const { object, property } = member;
if (t.isMemberExpression(object)) {
ids = ids.concat(getIdsFromMemberProps(object));
}
if (t.isThisExpression(object)) {
ids.push('this');
}
if (t.isIdentifier(object)) {
ids.push(object.name);
}
if (t.isIdentifier(property)) {
ids.push(property.name);
}
return ids;
}
/**
* TS 编译器会把 class property 移到构造器
* 而小程序要求 `config` 和所有函数在初始化(after new Class)之后就收集到所有的函数和 config 信息
* 所以当如构造器里有 this.func = () => {...} 的形式就给他转换成普通的 classProperty function
* 如果有 config 就给他还原
*/
function resetTSClassProperty(body) {
for (const method of body) {
if (t.isClassMethod(method) && method.kind === 'constructor') {
if (t.isBlockStatement(method.body)) {
method.body.body = method.body.body.filter(statement => {
if (t.isExpressionStatement(statement) && t.isAssignmentExpression(statement.expression)) {
const expr = statement.expression;
const { left, right } = expr;
if (t.isMemberExpression(left) &&
t.isThisExpression(left.object) &&
t.isIdentifier(left.property)) {
if ((t.isArrowFunctionExpression(right) || t.isFunctionExpression(right))
||
(left.property.name === 'config' && t.isObjectExpression(right))) {
const classProp = t.classProperty(left.property, right);
body.push(classProp);
handleThirdPartyComponent(classProp);
return false;
}
}
}
return true;
});
}
}
}
}
function findDeclarationScope(path, id) {
const scopePath = path.findParent(p => !!p.scope.getOwnBindingIdentifier(id.name));
if (scopePath) {
return scopePath;
}
throw utils_1.codeFrameError(path.node, '该引用从未被定义');
}
function buildFullPathThisPropsRef(id, memberIds, path) {
const scopePath = findDeclarationScope(path, id);
const binding = scopePath.scope.getOwnBinding(id.name);
if (binding) {
const bindingPath = binding.path;
if (bindingPath.isVariableDeclarator()) {
const dclId = bindingPath.get('id');
const dclInit = bindingPath.get('init');
let dclInitIds = [];
if (dclInit.isMemberExpression()) {
dclInitIds = getIdsFromMemberProps(dclInit.node);
if (dclId.isIdentifier()) {
memberIds.shift();
}
if (dclInitIds[0] === 'this' && dclInitIds[1] === 'props') {
return template(dclInitIds.concat(memberIds).join('.'))().expression;
}
}
}
}
}
function handleThirdPartyComponent(expr) {
if (t.isClassProperty(expr) && expr.key.name === 'config' && t.isObjectExpression(expr.value)) {
const properties = expr.value.properties;
for (const prop of properties) {
if (t.isObjectProperty(prop) &&
(t.isIdentifier(prop.key, { name: 'usingComponents' }) || t.isStringLiteral(prop.key, { value: 'usingComponents' })) &&
t.isObjectExpression(prop.value)) {
for (const value of prop.value.properties) {
if (t.isObjectProperty(value)) {
if (t.isStringLiteral(value.key)) {
constant_1.THIRD_PARTY_COMPONENTS.add(value.key.value);
}
if (t.isIdentifier(value.key)) {
constant_1.THIRD_PARTY_COMPONENTS.add(value.key.name);
}
}
}
}
}
}
}
function transform(options) {
if (options.adapter) {
adapter_1.setAdapter(options.adapter);
}
if (adapter_1.Adapter.type === "swan" /* swan */) {
constant_1.setLoopOriginal('privateOriginal');
}
constant_1.THIRD_PARTY_COMPONENTS.clear();
const code = options.isTyped
? ts.transpile(options.code, {
jsx: ts.JsxEmit.Preserve,
target: ts.ScriptTarget.ESNext,
importHelpers: true,
noEmitHelpers: true
})
: options.code;
options.env = Object.assign({ 'process.env.TARO_ENV': options.adapter || 'weapp' }, options.env || {});
options_1.setTransformOptions(options);
utils_1.setting.sourceCode = code;
// babel-traverse 无法生成 Hub
// 导致 Path#getSource|buildCodeFrameError 都无法直接使用
// 原因大概是 babylon.parse 没有生成 File 实例导致 scope 和 path 原型上都没有 `file`
// 将来升级到 babel@7 可以直接用 parse 而不是 transform
const ast = babel_core_1.transform(code, options_1.buildBabelTransformOptions()).ast;
if (options.isNormal) {
return { ast };
}
// transformFromAst(ast, code)
let result;
const componentSourceMap = new Map();
const imageSource = new Set();
const importSources = new Set();
let componentProperies = [];
let mainClass;
let storeName;
let renderMethod;
let isImportTaro = false;
babel_traverse_1.default(ast, {
TemplateLiteral(path) {
const nodes = [];
const { quasis, expressions } = path.node;
let index = 0;
if (path.parentPath.isTaggedTemplateExpression()) {
return;
}
for (const elem of quasis) {
if (elem.value.cooked) {
nodes.push(t.stringLiteral(elem.value.cooked));
}
if (index < expressions.length) {
const expr = expressions[index++];
if (!t.isStringLiteral(expr, { value: '' })) {
nodes.push(expr);
}
}
}
// + 号连接符必须保证第一和第二个 node 都是字符串
if (!t.isStringLiteral(nodes[0]) && !t.isStringLiteral(nodes[1])) {
nodes.unshift(t.stringLiteral(''));
}
let root = nodes[0];
for (let i = 1; i < nodes.length; i++) {
root = t.binaryExpression('+', root, nodes[i]);
}
path.replaceWith(root);
},
ClassDeclaration(path) {
mainClass = path;
const superClass = utils_1.getSuperClassCode(path);
if (superClass) {
try {
componentProperies = transform({
isRoot: false,
isApp: false,
code: superClass.code,
isTyped: true,
sourcePath: superClass.sourcePath,
outputPath: superClass.sourcePath
}).componentProperies;
}
catch (error) {
//
}
}
},
ClassExpression(path) {
mainClass = path;
},
ClassMethod(path) {
if (t.isIdentifier(path.node.key) && path.node.key.name === 'render') {
renderMethod = path;
}
},
IfStatement(path) {
const consequent = path.get('consequent');
if (!consequent.isBlockStatement()) {
consequent.replaceWith(t.blockStatement([
consequent.node
]));
}
},
CallExpression(path) {
const callee = path.get('callee');
if (utils_1.isContainJSXElement(path)) {
return;
}
if (callee.isReferencedMemberExpression()) {
const id = utils_1.findFirstIdentifierFromMemberExpression(callee.node);
const property = callee.node.property;
if (t.isIdentifier(property) && property.name.startsWith('on')) {
const funcExpr = path.findParent(p => p.isFunctionExpression());
if (funcExpr && funcExpr.isFunctionExpression()) {
const taroAPI = funcExpr.findParent(p => p.isCallExpression() && t.isMemberExpression(p.node.callee) && t.isIdentifier(p.node.callee.object, { name: 'Taro' }));
if (taroAPI && taroAPI.isCallExpression()) {
throw utils_1.codeFrameError(funcExpr.node, '在回调函数使用从 props 传递的函数时,请把回调函数改造为箭头函数并一直使用 `this` 取值');
}
}
}
const calleeIds = getIdsFromMemberProps(callee.node);
if (t.isIdentifier(id) && id.name.startsWith('on') && "alipay" /* alipay */ !== adapter_1.Adapter.type) {
const fullPath = buildFullPathThisPropsRef(id, calleeIds, path);
if (fullPath) {
path.replaceWith(t.callExpression(fullPath, path.node.arguments));
}
}
}
if (callee.isReferencedIdentifier()) {
const id = callee.node;
const ids = [id.name];
if (t.isIdentifier(id) && id.name.startsWith('on')) {
const funcExpr = path.findParent(p => p.isFunctionExpression());
if (funcExpr && funcExpr.isFunctionExpression()) {
const taroAPI = funcExpr.findParent(p => p.isCallExpression() && t.isMemberExpression(p.node.callee) && t.isIdentifier(p.node.callee.object, { name: 'Taro' }));
if (taroAPI && taroAPI.isCallExpression()) {
throw utils_1.codeFrameError(funcExpr.node, '在回调函数使用从 props 传递的函数时,请把回调函数改造为箭头函数并一直使用 `this` 取值');
}
}
const fullPath = buildFullPathThisPropsRef(id, ids, path);
if (fullPath) {
path.replaceWith(t.callExpression(fullPath, path.node.arguments));
}
}
}
},
// JSXIdentifier (path) {
// const parentPath = path.parentPath
// if (!parentPath.isJSXAttribute()) {
// return
// }
// const element = parentPath.parentPath
// if (!element.isJSXOpeningElement()) {
// return
// }
// const elementName = element.get('name')
// if (!elementName.isJSXIdentifier()) {
// return
// }
// if (DEFAULT_Component_SET.has(elementName.node.name)) {
// return
// }
// const expr = parentPath.get('value.expression')
// },
JSXElement(path) {
const assignment = path.findParent(p => p.isAssignmentExpression());
if (assignment && assignment.isAssignmentExpression() && !options.isTyped) {
const left = assignment.node.left;
if (t.isIdentifier(left)) {
const binding = assignment.scope.getBinding(left.name);
if (binding && binding.scope === assignment.scope) {
if (binding.path.isVariableDeclarator()) {
binding.path.node.init = path.node;
assignment.remove();
}
else {
throw utils_1.codeFrameError(path.node, '同一个作用域的JSX 变量延时赋值没有意义。详见https://github.com/NervJS/taro/issues/550');
}
}
}
}
const switchStatement = path.findParent(p => p.isSwitchStatement());
if (switchStatement && switchStatement.isSwitchStatement()) {
const { discriminant, cases } = switchStatement.node;
const ifStatement = cases.map((Case, index) => {
const [consequent] = Case.consequent;
if (!t.isBlockStatement(consequent)) {
throw utils_1.codeFrameError(switchStatement.node, '含有 JSX 的 switch case 语句必须每种情况都用花括号 `{}` 包裹结果');
}
const block = t.blockStatement(consequent.body.filter(b => !t.isBreakStatement(b)));
if (index !== cases.length - 1 && t.isNullLiteral(Case.test)) {
throw utils_1.codeFrameError(Case, '含有 JSX 的 switch case 语句只有最后一个 case 才能是 default');
}
const test = Case.test === null ? t.nullLiteral() : t.binaryExpression('===', discriminant, Case.test);
return { block, test };
}).reduceRight((ifStatement, item) => {
if (t.isNullLiteral(item.test)) {
ifStatement.alternate = item.block;
return ifStatement;
}
const newStatement = t.ifStatement(item.test, item.block, t.isBooleanLiteral(ifStatement.test, { value: false })
? ifStatement.alternate
: ifStatement);
return newStatement;
}, t.ifStatement(t.booleanLiteral(false), t.blockStatement([])));
switchStatement.insertAfter(ifStatement);
switchStatement.remove();
}
const isForStatement = (p) => p && (p.isForStatement() || p.isForInStatement() || p.isForOfStatement());
const forStatement = path.findParent(isForStatement);
if (isForStatement(forStatement)) {
throw utils_1.codeFrameError(forStatement.node, '不行使用 for 循环操作 JSX 元素详情https://github.com/NervJS/taro/blob/master/packages/eslint-plugin-taro/docs/manipulate-jsx-as-array.md');
}
const loopCallExpr = path.findParent(p => utils_1.isArrayMapCallExpression(p));
if (loopCallExpr && loopCallExpr.isCallExpression()) {
const [func] = loopCallExpr.node.arguments;
if (t.isArrowFunctionExpression(func) && !t.isBlockStatement(func.body)) {
func.body = t.blockStatement([
t.returnStatement(func.body)
]);
}
}
},
JSXOpeningElement(path) {
const { name } = path.node.name;
if (name === 'Provider') {
const modules = path.scope.getAllBindings('module');
const providerBinding = Object.values(modules).some((m) => m.identifier.name === 'Provider');
if (providerBinding) {
path.node.name = t.jSXIdentifier('View');
const store = path.node.attributes.find(attr => attr.name.name === 'store');
if (store && t.isJSXExpressionContainer(store.value) && t.isIdentifier(store.value.expression)) {
storeName = store.value.expression.name;
}
path.node.attributes = [];
}
}
if (constant_1.IMAGE_COMPONENTS.has(name)) {
for (const attr of path.node.attributes) {
if (attr.name.name === 'src') {
if (t.isStringLiteral(attr.value)) {
imageSource.add(attr.value.value);
}
else if (t.isJSXExpressionContainer(attr.value)) {
if (t.isStringLiteral(attr.value.expression)) {
imageSource.add(attr.value.expression.value);
}
}
}
}
}
},
JSXAttribute(path) {
const { name, value } = path.node;
if (!t.isJSXIdentifier(name) || value === null || t.isStringLiteral(value) || t.isJSXElement(value)) {
return;
}
const expr = value.expression;
const exprPath = path.get('value.expression');
const classDecl = path.findParent(p => p.isClassDeclaration());
const classDeclName = classDecl && classDecl.isClassDeclaration() && lodash_1.get(classDecl, 'node.id.name', '');
let isConverted = false;
if (classDeclName) {
isConverted = classDeclName === '_C' || classDeclName.endsWith('Tmpl');
}
if (!t.isBinaryExpression(expr, { operator: '+' }) && !t.isLiteral(expr) && name.name === 'style' && !isConverted) {
const jsxID = path.findParent(p => p.isJSXOpeningElement()).get('name');
if (jsxID && jsxID.isJSXIdentifier() && constant_1.DEFAULT_Component_SET.has(jsxID.node.name)) {
exprPath.replaceWith(t.callExpression(t.identifier(constant_1.INTERNAL_INLINE_STYLE), [expr]));
}
}
if (name.name.startsWith('on')) {
if (exprPath.isReferencedIdentifier()) {
const ids = [expr.name];
const fullPath = buildFullPathThisPropsRef(expr, ids, path);
if (fullPath) {
exprPath.replaceWith(fullPath);
}
}
if (exprPath.isReferencedMemberExpression()) {
const id = utils_1.findFirstIdentifierFromMemberExpression(expr);
const ids = getIdsFromMemberProps(expr);
if (t.isIdentifier(id)) {
const fullPath = buildFullPathThisPropsRef(id, ids, path);
if (fullPath) {
exprPath.replaceWith(fullPath);
}
}
}
// @TODO: bind 的处理待定
}
},
ImportDeclaration(path) {
const source = path.node.source.value;
if (importSources.has(source)) {
throw utils_1.codeFrameError(path.node, '无法在同一文件重复 import 相同的包。');
}
else {
importSources.add(source);
}
const names = [];
if (source === constant_1.TARO_PACKAGE_NAME) {
isImportTaro = true;
path.node.specifiers.push(t.importSpecifier(t.identifier(constant_1.INTERNAL_SAFE_GET), t.identifier(constant_1.INTERNAL_SAFE_GET)), t.importSpecifier(t.identifier(constant_1.INTERNAL_GET_ORIGNAL), t.identifier(constant_1.INTERNAL_GET_ORIGNAL)), t.importSpecifier(t.identifier(constant_1.INTERNAL_INLINE_STYLE), t.identifier(constant_1.INTERNAL_INLINE_STYLE)), t.importSpecifier(t.identifier(constant_1.GEL_ELEMENT_BY_ID), t.identifier(constant_1.GEL_ELEMENT_BY_ID)));
}
if (source === constant_1.REDUX_PACKAGE_NAME || source === constant_1.MOBX_PACKAGE_NAME) {
path.node.specifiers.forEach((s, index, specs) => {
if (s.local.name === 'Provider') {
specs.splice(index, 1);
specs.push(t.importSpecifier(t.identifier('setStore'), t.identifier('setStore')));
}
});
}
path.traverse({
ImportDefaultSpecifier(path) {
const name = path.node.local.name;
constant_1.DEFAULT_Component_SET.has(name) || names.push(name);
},
ImportSpecifier(path) {
const name = path.node.imported.name;
constant_1.DEFAULT_Component_SET.has(name) || names.push(name);
if (source === constant_1.TARO_PACKAGE_NAME && name === 'Component') {
path.node.local = t.identifier('__BaseComponent');
}
}
});
componentSourceMap.set(source, names);
}
});
if (!isImportTaro) {
ast.program.body.unshift(t.importDeclaration([
t.importDefaultSpecifier(t.identifier('Taro')),
t.importSpecifier(t.identifier(constant_1.INTERNAL_SAFE_GET), t.identifier(constant_1.INTERNAL_SAFE_GET)),
t.importSpecifier(t.identifier(constant_1.INTERNAL_GET_ORIGNAL), t.identifier(constant_1.INTERNAL_GET_ORIGNAL)),
t.importSpecifier(t.identifier(constant_1.INTERNAL_INLINE_STYLE), t.identifier(constant_1.INTERNAL_INLINE_STYLE))
], t.stringLiteral('@tarojs/taro')));
}
if (!mainClass) {
throw new Error('未找到 Taro.Component 的类定义');
}
mainClass.node.body.body.forEach(handleThirdPartyComponent);
const storeBinding = mainClass.scope.getBinding(storeName);
mainClass.scope.rename('Component', '__BaseComponent');
if (storeBinding) {
const statementPath = storeBinding.path.getStatementParent();
if (statementPath) {
ast.program.body.forEach((node, index, body) => {
if (node === statementPath.node) {
body.splice(index + 1, 0, t.expressionStatement(t.callExpression(t.identifier('setStore'), [
t.identifier(storeName)
])));
}
});
}
}
resetTSClassProperty(mainClass.node.body.body);
if (options.isApp) {
renderMethod.replaceWith(t.classMethod('method', t.identifier('_createData'), [], t.blockStatement([])));
return { ast };
}
//@fix add arg => componentSourceMap
result = new class_1.Transformer(mainClass, options.sourcePath, componentProperies, componentSourceMap).result;
result.code = babel_generator_1.default(ast).code;
result.ast = ast;
const lessThanSignReg = new RegExp(constant_1.lessThanSignPlacehold, 'g');
result.compressedTemplate = result.template;
result.template = html_1.prettyPrint(result.template, {
max_char: 0,
unformatted: process.env.NODE_ENV === 'test' ? [] : ['text']
});
result.template = result.template.replace(lessThanSignReg, '<');
result.imageSrcs = Array.from(imageSource);
return result;
}
exports.default = transform;
//# sourceMappingURL=index.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,258 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const babel_generator_1 = require("babel-generator");
const t = require("babel-types");
const lodash_1 = require("lodash");
const constant_1 = require("./constant");
const create_html_element_1 = require("./create-html-element");
const utils_1 = require("./utils");
const adapter_1 = require("./adapter");
function isStartWithWX(str) {
return str[0] === 'w' && str[1] === 'x';
}
exports.isStartWithWX = isStartWithWX;
const specialComponentName = ['block', 'Block', 'slot', 'Slot'];
function removeJSXThisProperty(path) {
if (!path.parentPath.isCallExpression()) {
const p = path.getSibling('property');
if (p.isIdentifier({ name: 'props' }) ||
p.isIdentifier({ name: 'data' })) {
path.parentPath.replaceWithSourceString('this');
}
else {
path.parentPath.replaceWith(p);
}
}
}
exports.removeJSXThisProperty = removeJSXThisProperty;
function findJSXAttrByName(attrs, name) {
for (const attr of attrs) {
if (!t.isJSXIdentifier(attr.name)) {
break;
}
if (attr.name.name === name) {
return attr;
}
}
return null;
}
exports.findJSXAttrByName = findJSXAttrByName;
function buildRefTemplate(name, refName, loop, key) {
const attrs = [
t.jSXAttribute(t.jSXIdentifier('is'), t.stringLiteral(name)),
t.jSXAttribute(t.jSXIdentifier('data'), t.stringLiteral(`{{...${refName ? `${loop ? '' : '$$'}${refName}` : '__data'}}}`))
];
if (key) {
attrs.push(key);
}
return t.jSXElement(t.jSXOpeningElement(t.jSXIdentifier('template'), attrs), t.jSXClosingElement(t.jSXIdentifier('template')), []);
}
exports.buildRefTemplate = buildRefTemplate;
function buildJSXAttr(name, value) {
return t.jSXAttribute(t.jSXIdentifier(name), t.jSXExpressionContainer(value));
}
exports.buildJSXAttr = buildJSXAttr;
function newJSXIfAttr(jsx, value) {
jsx.openingElement.attributes.push(buildJSXAttr(adapter_1.Adapter.if, value));
}
exports.newJSXIfAttr = newJSXIfAttr;
function setJSXAttr(jsx, name, value, path) {
const element = jsx.openingElement;
if (!t.isJSXIdentifier(element.name)) {
return;
}
if (element.name.name === 'Block' || element.name.name === 'block' || !path) {
jsx.openingElement.attributes.push(t.jSXAttribute(t.jSXIdentifier(name), value));
}
else {
const block = buildBlockElement();
setJSXAttr(block, name, value);
block.children = [jsx];
path.node = block;
}
}
exports.setJSXAttr = setJSXAttr;
function isAllLiteral(...args) {
return args.every(p => t.isLiteral(p));
}
exports.isAllLiteral = isAllLiteral;
function buildBlockElement() {
return t.jSXElement(t.jSXOpeningElement(t.jSXIdentifier('block'), []), t.jSXClosingElement(t.jSXIdentifier('block')), []);
}
exports.buildBlockElement = buildBlockElement;
function parseJSXChildren(children) {
return children
.filter(child => {
return !(t.isJSXText(child) && child.value.trim() === '');
})
.reduce((str, child) => {
if (t.isJSXText(child)) {
const strings = [];
child.value.split(/(\r?\n\s*)/).forEach((val) => {
const value = val.replace(/\u00a0/g, '&nbsp;').trimLeft();
if (!value) {
return;
}
if (value.startsWith('\n')) {
return;
}
strings.push(value);
});
return str + strings.join('');
}
if (t.isJSXElement(child)) {
return str + parseJSXElement(child);
}
if (t.isJSXExpressionContainer(child)) {
if (t.isJSXElement(child.expression)) {
return str + parseJSXElement(child.expression);
}
return str + `{${utils_1.decodeUnicode(babel_generator_1.default(child, {
quotes: 'single',
jsonCompatibleStrings: true
})
.code)
.replace(/(this\.props\.)|(this\.data\.)/g, '')
.replace(/(props\.)|(data\.)/g, '')
.replace(/this\./g, '')
.replace(/</g, constant_1.lessThanSignPlacehold)}}`;
}
return str;
}, '');
}
function parseJSXElement(element) {
const children = element.children;
const { attributes, name } = element.openingElement;
//const TRIGGER_OBSERER = adapter_1.Adapter.type === "swan" /* swan */ ? 'privateTriggerObserer' : '__triggerObserer';
if (t.isJSXMemberExpression(name)) {
throw utils_1.codeFrameError(name.loc, '暂不支持 JSX 成员表达式');
}
const componentName = name.name;
const isDefaultComponent = constant_1.DEFAULT_Component_SET.has(componentName);
const componentSpecialProps = constant_1.SPECIAL_COMPONENT_PROPS.get(componentName);
const componentTransfromProps = constant_1.TRANSFORM_COMPONENT_PROPS.get(adapter_1.Adapter.type);
let hasElseAttr = false;
attributes.forEach((a, index) => {
if (a.name.name === adapter_1.Adapter.else && !['block', 'Block'].includes(componentName) && !isDefaultComponent) {
hasElseAttr = true;
attributes.splice(index, 1);
}
});
if (hasElseAttr) {
return create_html_element_1.createHTMLElement({
name: 'block',
attributes: {
[adapter_1.Adapter.else]: true
},
value: parseJSXChildren([element])
});
}
let attributesTrans = {};
if (attributes.length) {
attributesTrans = attributes.reduce((obj, attr) => {
if (t.isJSXSpreadAttribute(attr)) {
throw utils_1.codeFrameError(attr.loc, 'JSX 参数暂不支持 ...spread 表达式');
}
let name = attr.name.name;
if (constant_1.DEFAULT_Component_SET.has(componentName)) {
if (name === 'className') {
name = 'class';
}
}
let value = true;
let attrValue = attr.value;
if (typeof name === 'string') {
const isAlipayEvent = adapter_1.Adapter.type === "alipay" /* alipay */ && /(^on[A-Z_])|(^catch[A-Z_])/.test(name);
if (t.isStringLiteral(attrValue)) {
value = attrValue.value;
}
else if (t.isJSXExpressionContainer(attrValue)) {
let isBindEvent = (name.startsWith('bind') && name !== 'bind') || (name.startsWith('catch') && name !== 'catch');
let code = utils_1.decodeUnicode(babel_generator_1.default(attrValue.expression, {
quotes: 'single',
concise: true
}).code)
.replace(/"/g, "'")
.replace(/(this\.props\.)|(this\.data\.)/g, '')
.replace(/this\./g, '');
if ("swan" /* swan */ === adapter_1.Adapter.type &&
code !== 'true' &&
code !== 'false' &&
constant_1.swanSpecialAttrs[componentName] &&
constant_1.swanSpecialAttrs[componentName].includes(name)) {
value = `{= ${code} =}`;
}
else {
if (adapter_1.Adapter.key === name) {
const splitCode = code.split('.');
if (splitCode.length > 1) {
value = splitCode.slice(1).join('.');
}
else {
value = code;
}
}
else {
value = isBindEvent || isAlipayEvent ? code : `{{${code}}}`;
}
}
if (adapter_1.Adapter.type === "swan" /* swan */ && name === adapter_1.Adapter.for) {
value = code;
}
if (t.isStringLiteral(attrValue.expression)) {
value = attrValue.expression.value;
}
}
else if (attrValue === null && name !== adapter_1.Adapter.else) {
value = `{{true}}`;
}
if (constant_1.THIRD_PARTY_COMPONENTS.has(componentName) && /^bind/.test(name) && name.includes('-')) {
name = name.replace(/^bind/, 'bind:');
}
if (componentTransfromProps && componentTransfromProps[componentName]) {
const transfromProps = componentTransfromProps[componentName];
Object.keys(transfromProps).forEach(oriName => {
if (transfromProps.hasOwnProperty(name)) {
name = transfromProps[oriName];
}
});
}
if ((componentName === 'Input' || componentName === 'input') && name === 'maxLength') {
obj['maxlength'] = value;
}
else if (componentSpecialProps && componentSpecialProps.has(name) ||
name.startsWith('__fn_') ||
isAlipayEvent) {
obj[name] = value;
}
else {
obj[isDefaultComponent && !name.includes('-') && !name.includes(':') ? lodash_1.kebabCase(name) : name] = value;
}
}
if (!isDefaultComponent && !specialComponentName.includes(componentName)) {
//obj[TRIGGER_OBSERER] = '{{ _triggerObserer }}';
}
return obj;
}, {});
}
else if (!isDefaultComponent && !specialComponentName.includes(componentName)) {
//attributesTrans[TRIGGER_OBSERER] = '{{ _triggerObserer }}';
}
return create_html_element_1.createHTMLElement({
name: lodash_1.kebabCase(componentName),
attributes: attributesTrans,
value: parseJSXChildren(children)
});
}
exports.parseJSXElement = parseJSXElement;
function generateHTMLTemplate(template, name) {
return create_html_element_1.createHTMLElement({
name: 'template',
attributes: {
name
},
value: parseJSXElement(template)
});
}
exports.generateHTMLTemplate = generateHTMLTemplate;
//# sourceMappingURL=jsx.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,33 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var Lifecycle;
(function (Lifecycle) {
Lifecycle["constructor"] = "constructor";
Lifecycle["componentWillMount"] = "componentWillMount";
Lifecycle["componentDidMount"] = "componentDidMount";
Lifecycle["componentWillUpdate"] = "componentWillUpdate";
Lifecycle["componentDidUpdate"] = "componentDidUpdate";
Lifecycle["componentWillUnmount"] = "componentWillUnmount";
Lifecycle["componentDidCatch"] = "componentDidCatch";
Lifecycle["componentDidShow"] = "componentDidShow";
Lifecycle["componentDidHide"] = "componentDidHide";
Lifecycle["componentDidAttached"] = "componentDidAttached";
Lifecycle["componentDidMoved"] = "componentDidMoved";
Lifecycle["shouldComponentUpdate"] = "shouldComponentUpdate";
Lifecycle["componentWillReceiveProps"] = "componentWillReceiveProps";
})(Lifecycle = exports.Lifecycle || (exports.Lifecycle = {}));
exports.PageLifecycle = {
[Lifecycle.componentDidMount]: 'onLaunch',
[Lifecycle.componentWillMount]: 'onLoad',
[Lifecycle.componentWillUnmount]: 'onUnload',
[Lifecycle.componentDidShow]: 'onShow',
[Lifecycle.componentDidHide]: 'onHide'
};
exports.ComponentLifeCycle = {
[Lifecycle.componentWillMount]: 'created',
[Lifecycle.componentDidAttached]: 'attached',
[Lifecycle.componentDidMount]: 'ready',
[Lifecycle.componentDidMoved]: 'moved',
[Lifecycle.componentWillUnmount]: 'detached'
};
//# sourceMappingURL=lifecycle.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"lifecycle.js","sourceRoot":"","sources":["../../src/lifecycle.ts"],"names":[],"mappings":";;AAAA,IAAY,SAcX;AAdD,WAAY,SAAS;IACnB,wCAA2B,CAAA;IAC3B,sDAAyC,CAAA;IACzC,oDAAuC,CAAA;IACvC,wDAA2C,CAAA;IAC3C,sDAAyC,CAAA;IACzC,0DAA6C,CAAA;IAC7C,oDAAuC,CAAA;IACvC,kDAAqC,CAAA;IACrC,kDAAqC,CAAA;IACrC,0DAA6C,CAAA;IAC7C,oDAAuC,CAAA;IACvC,4DAA+C,CAAA;IAC/C,oEAAuD,CAAA;AACzD,CAAC,EAdW,SAAS,GAAT,iBAAS,KAAT,iBAAS,QAcpB;AAEY,QAAA,aAAa,GAAG;IAC3B,CAAC,SAAS,CAAC,iBAAiB,CAAC,EAAE,UAAU;IACzC,CAAC,SAAS,CAAC,kBAAkB,CAAC,EAAE,QAAQ;IACxC,CAAC,SAAS,CAAC,oBAAoB,CAAC,EAAE,UAAU;IAC5C,CAAC,SAAS,CAAC,gBAAgB,CAAC,EAAE,QAAQ;IACtC,CAAC,SAAS,CAAC,gBAAgB,CAAC,EAAE,QAAQ;CACvC,CAAA;AAEY,QAAA,kBAAkB,GAAG;IAChC,CAAC,SAAS,CAAC,kBAAkB,CAAC,EAAE,SAAS;IACzC,CAAC,SAAS,CAAC,oBAAoB,CAAC,EAAE,UAAU;IAC5C,CAAC,SAAS,CAAC,iBAAiB,CAAC,EAAE,OAAO;IACtC,CAAC,SAAS,CAAC,iBAAiB,CAAC,EAAE,OAAO;IACtC,CAAC,SAAS,CAAC,oBAAoB,CAAC,EAAE,UAAU;CAC7C,CAAA"}

View File

@ -0,0 +1,227 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const t = require("babel-types");
const utils_1 = require("./utils");
const jsx_1 = require("./jsx");
const constant_1 = require("./constant");
const adapter_1 = require("./adapter");
// @TODO
// 重构 parseRender 和 parseLoop 失败
// 尚不清楚 babel 的 data 和 context 传参机制
// 目前先写两份代码,有时间看看 babel 具体对 data 和 context 做了什么导致传参失败
function parseLoopBody(body, jsxDeclarations,
// @TODO
// 把 templates 换成 Map 可以支持 shalow variables declared
// 现在先用 ESLint 的 no-shalow 顶着
templates, loopScopes, finalReturnElement, returnedPaths) {
const bodyScope = body.scope;
body.traverse({
JSXElement(jsxElementPath) {
const parentNode = jsxElementPath.parent;
const parentPath = jsxElementPath.parentPath;
const isFinalReturn = jsxElementPath.getFunctionParent().isClassMethod();
const isJSXChildren = t.isJSXElement(parentNode);
if (!isJSXChildren) {
let statementParent = jsxElementPath.getStatementParent();
if (!(statementParent.isVariableDeclaration() ||
statementParent.isExpressionStatement())) {
statementParent = statementParent.findParent(s => s.isVariableDeclaration() || s.isExpressionStatement());
}
jsxDeclarations.add(statementParent);
if (t.isVariableDeclarator(parentNode)) {
if (statementParent) {
const name = utils_1.findIdentifierFromStatement(statementParent.node);
// setTemplate(name, path, templates)
name && templates.set(name, jsxElementPath.node);
}
}
else if (t.isLogicalExpression(parentNode)) {
const { left, operator } = parentNode;
if (operator === '&&') {
if (t.isExpression(left)) {
utils_1.newJSXIfAttr(jsxElementPath.node, left);
parentPath.replaceWith(jsxElementPath.node);
if (statementParent) {
const name = utils_1.findIdentifierFromStatement(statementParent.node);
utils_1.setTemplate(name, jsxElementPath, templates);
// name && templates.set(name, path.node)
}
}
}
}
else if (t.isConditionalExpression(parentNode)) {
const { test, consequent, alternate } = parentNode;
const block = jsx_1.buildBlockElement();
if (t.isJSXElement(consequent) && t.isLiteral(alternate)) {
const { value, confident } = parentPath.get('alternate').evaluate();
if (confident && !value) {
utils_1.newJSXIfAttr(block, test);
block.children = [jsxElementPath.node];
// newJSXIfAttr(jsxElementPath.node, test)
parentPath.replaceWith(block);
if (statementParent) {
const name = utils_1.findIdentifierFromStatement(statementParent.node);
utils_1.setTemplate(name, jsxElementPath, templates);
// name && templates.set(name, path.node)
}
}
}
else if (t.isLiteral(consequent) && t.isJSXElement(consequent)) {
if (t.isNullLiteral(consequent)) {
utils_1.newJSXIfAttr(block, utils_1.reverseBoolean(test));
// newJSXIfAttr(jsxElementPath.node, reverseBoolean(test))
parentPath.replaceWith(block);
if (statementParent) {
const name = utils_1.findIdentifierFromStatement(statementParent.node);
utils_1.setTemplate(name, jsxElementPath, templates);
// name && templates.set(name, path.node)
}
}
}
else if (t.isJSXElement(consequent) && t.isJSXElement(alternate)) {
const block2 = jsx_1.buildBlockElement();
block.children = [consequent];
utils_1.newJSXIfAttr(block, test);
jsx_1.setJSXAttr(block2, adapter_1.Adapter.else);
block2.children = [alternate];
const parentBlock = jsx_1.buildBlockElement();
parentBlock.children = [block, block2];
parentPath.replaceWith(parentBlock);
if (statementParent) {
const name = utils_1.findIdentifierFromStatement(statementParent.node);
utils_1.setTemplate(name, jsxElementPath, templates);
}
}
else {
// console.log('todo')
}
}
else if (t.isReturnStatement(parentNode)) {
if (!isFinalReturn) {
const caller = parentPath.findParent(p => p.isCallExpression());
if (caller.isCallExpression()) {
const callee = caller.node.callee;
if (t.isMemberExpression(callee) &&
t.isIdentifier(callee.property) &&
callee.property.name === 'map') {
let ary = callee.object;
const blockStatementPath = parentPath.findParent(p => p.isBlockStatement());
const body = blockStatementPath.node.body;
let stateToBeAssign = new Set();
for (const statement of body) {
if (t.isVariableDeclaration(statement)) {
for (const dcl of statement.declarations) {
if (t.isIdentifier(dcl.id)) {
const scope = blockStatementPath.scope;
const stateName = scope.generateUid(constant_1.LOOP_STATE);
stateToBeAssign.add(stateName);
blockStatementPath.scope.rename(dcl.id.name, stateName);
}
}
}
}
if (t.isCallExpression(ary) || utils_1.isContainFunction(caller.get('callee').get('object'))) {
const variableName = `anonymousState_${bodyScope.generateUid()}`;
caller.getStatementParent().insertBefore(utils_1.buildConstVariableDeclaration(variableName, ary));
ary = t.identifier(variableName);
}
jsx_1.setJSXAttr(jsxElementPath.node, adapter_1.Adapter.for, t.jSXExpressionContainer(ary));
const [func] = caller.node.arguments;
if (t.isFunctionExpression(func) ||
t.isArrowFunctionExpression(func)) {
const [item, index] = func.params;
if (t.isIdentifier(item)) {
jsx_1.setJSXAttr(jsxElementPath.node, adapter_1.Adapter.forItem, t.stringLiteral(item.name));
loopScopes.add(item.name);
}
else {
jsx_1.setJSXAttr(jsxElementPath.node, adapter_1.Adapter.forItem, t.stringLiteral('__item'));
}
if (t.isIdentifier(index)) {
jsx_1.setJSXAttr(jsxElementPath.node, adapter_1.Adapter.forIndex, t.stringLiteral(index.name));
loopScopes.add(index.name);
}
caller.replaceWith(jsxElementPath.node);
if (statementParent) {
const name = utils_1.findIdentifierFromStatement(statementParent.node);
// setTemplate(name, path, templates)
name && templates.set(name, jsxElementPath.node);
}
}
}
}
}
else {
const ifStatement = parentPath.findParent(p => p.isIfStatement());
const blockStatement = parentPath.findParent(p => p.isBlockStatement());
const block = finalReturnElement || jsx_1.buildBlockElement();
if (utils_1.isBlockIfStatement(ifStatement, blockStatement)) {
const { test, alternate, consequent } = ifStatement.node;
if (alternate === blockStatement.node) {
throw utils_1.codeFrameError(parentNode.loc, '不必要的 else 分支,请遵从 ESLint consistent-return: https://eslint.org/docs/rules/consistent-return');
}
else if (consequent === blockStatement.node) {
const parentIfStatement = ifStatement.findParent(p => p.isIfStatement());
if (parentIfStatement) {
jsx_1.setJSXAttr(jsxElementPath.node, adapter_1.Adapter.elseif, t.jSXExpressionContainer(test));
}
else {
utils_1.newJSXIfAttr(jsxElementPath.node, test);
}
}
}
else if (block.children.length !== 0) {
jsx_1.setJSXAttr(jsxElementPath.node, adapter_1.Adapter.else);
}
block.children.push(jsxElementPath.node);
finalReturnElement = block;
returnedPaths.push(parentPath);
}
}
else if (t.isArrowFunctionExpression(parentNode)) {
//
}
else if (t.isAssignmentExpression(parentNode)) {
if (t.isIdentifier(parentNode.left)) {
const name = parentNode.left.name;
const bindingNode = bodyScope.getOwnBinding(name).path.node;
const block = templates.get(name) || jsx_1.buildBlockElement();
if (utils_1.isEmptyDeclarator(bindingNode)) {
const ifStatement = parentPath.findParent(p => p.isIfStatement());
const blockStatement = parentPath.findParent(p => p.isBlockStatement());
if (utils_1.isBlockIfStatement(ifStatement, blockStatement)) {
const { test, alternate, consequent } = ifStatement.node;
if (alternate === blockStatement.node) {
jsx_1.setJSXAttr(jsxElementPath.node, adapter_1.Adapter.else);
}
else if (consequent === blockStatement.node) {
const parentIfStatement = ifStatement.findParent(p => p.isIfStatement());
if (parentIfStatement && parentIfStatement.get('alternate') === ifStatement) {
jsx_1.setJSXAttr(jsxElementPath.node, adapter_1.Adapter.elseif, t.jSXExpressionContainer(test));
}
else {
if (parentIfStatement) {
utils_1.newJSXIfAttr(block, parentIfStatement.node.test);
}
utils_1.newJSXIfAttr(jsxElementPath.node, test);
}
}
block.children.push(jsxElementPath.node);
// setTemplate(name, path, templates)
name && templates.set(name, block);
}
}
else {
throw utils_1.codeFrameError(jsxElementPath.node.loc, '请将 JSX 赋值表达式初始化为 null然后再进行 if 条件表达式赋值。');
}
}
}
else if (!t.isJSXElement(parentNode)) {
// throwError(path, '考虑只对 JSX 元素赋值一次。')
}
}
}
});
}
exports.parseLoopBody = parseLoopBody;
//# sourceMappingURL=loop-component.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,37 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const eslint_1 = require("./eslint");
exports.transformOptions = {};
exports.setTransformOptions = (options) => {
for (const key in options) {
if (options.hasOwnProperty(key)) {
exports.transformOptions[key] = options[key];
}
}
};
exports.buildBabelTransformOptions = () => {
return {
parserOpts: {
sourceType: 'module',
plugins: [
'classProperties',
'jsx',
'flow',
'flowComment',
'trailingFunctionCommas',
'asyncFunctions',
'exponentiationOperator',
'asyncGenerators',
'objectRestSpread',
'decorators',
'dynamicImport'
]
},
plugins: [
require('babel-plugin-transform-flow-strip-types'),
[require('babel-plugin-transform-define').default, exports.transformOptions.env]
].concat(process.env.ESLINT === 'false' || exports.transformOptions.isNormal || exports.transformOptions.isTyped ? [] : eslint_1.eslintValidation)
.concat((process.env.NODE_ENV === 'test') ? [] : require('babel-plugin-remove-dead-code').default)
};
};
//# sourceMappingURL=options.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"options.js","sourceRoot":"","sources":["../../src/options.ts"],"names":[],"mappings":";;AACA,qCAA2C;AAe9B,QAAA,gBAAgB,GAAY,EAAS,CAAA;AAErC,QAAA,mBAAmB,GAAG,CAAC,OAAgB,EAAE,EAAE;IACtD,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE;QACzB,IAAI,OAAO,CAAC,cAAc,CAAC,GAAG,CAAC,EAAE;YAC/B,wBAAgB,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAA;SACrC;KACF;AACH,CAAC,CAAA;AAEY,QAAA,0BAA0B,GAA2B,GAAG,EAAE;IACrE,OAAO;QACL,UAAU,EAAE;YACV,UAAU,EAAE,QAAQ;YACpB,OAAO,EAAE;gBACP,iBAAiB;gBACjB,KAAK;gBACL,MAAM;gBACN,aAAa;gBACb,wBAAwB;gBACxB,gBAAgB;gBAChB,wBAAwB;gBACxB,iBAAiB;gBACjB,kBAAkB;gBAClB,YAAY;gBACZ,eAAe;aACP;SACX;QACD,OAAO,EAAE;YACP,OAAO,CAAC,yCAAyC,CAAC;YAClD,CAAC,OAAO,CAAC,+BAA+B,CAAC,CAAC,OAAO,EAAE,wBAAgB,CAAC,GAAG,CAAC;SACzE,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,KAAK,OAAO,IAAI,wBAAgB,CAAC,QAAQ,IAAI,wBAAgB,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,yBAAgB,CAAC;aACxH,MAAM,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,+BAA+B,CAAC,CAAC,OAAO,CAAC;KACnG,CAAA;AACH,CAAC,CAAA"}

View File

@ -0,0 +1,45 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const t = require("babel-types");
function isString(node) {
return t.isLiteral(node) && typeof node.value === 'string';
}
function buildBinaryExpression(left, right) {
return t.binaryExpression('+', left, right);
}
function templateLiterals(path, state) {
let nodes = [];
const expressions = path.get('expressions');
for (const elem of (path.node.quasis)) {
nodes.push(t.stringLiteral(elem.value.cooked));
const expr = expressions.shift();
if (expr) {
// tslint:disable-next-line:no-multi-spaces
if (state.opts.spec && !expr.isBaseType('string') && !expr.isBaseType('number')) {
nodes.push(t.callExpression(t.identifier('String'), [expr.node]));
}
else {
nodes.push(expr.node);
}
}
}
// filter out empty string literals
nodes = nodes.filter((n) => !t.isLiteral(n, { value: '' }));
// since `+` is left-to-right associative
// ensure the first node is a string if first/second isn't
if (!isString(nodes[0]) && !isString(nodes[1])) {
nodes.unshift(t.stringLiteral(''));
}
if (nodes.length > 1) {
let root = buildBinaryExpression(nodes.shift(), nodes.shift());
for (const node of nodes) {
root = buildBinaryExpression(root, node);
}
path.replaceWith(root);
}
else {
path.replaceWith(nodes[0]);
}
}
exports.templateLiterals = templateLiterals;
//# sourceMappingURL=plugins.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"plugins.js","sourceRoot":"","sources":["../../src/plugins.ts"],"names":[],"mappings":";;AAAA,iCAAgC;AAEhC,SAAS,QAAQ,CAAE,IAAI;IACrB,OAAO,CAAC,CAAC,SAAS,CAAC,IAAW,CAAC,IAAI,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ,CAAA;AACnE,CAAC;AAED,SAAS,qBAAqB,CAAE,IAAI,EAAE,KAAK;IACzC,OAAO,CAAC,CAAC,gBAAgB,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,CAAC,CAAA;AAC7C,CAAC;AACD,SAAgB,gBAAgB,CAAE,IAAI,EAAE,KAAK;IAE3C,IAAI,KAAK,GAAkB,EAAE,CAAA;IAE7B,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,CAAA;IAE3C,KAAK,MAAM,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE;QACrC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAA;QAE9C,MAAM,IAAI,GAAG,WAAW,CAAC,KAAK,EAAE,CAAA;QAChC,IAAI,IAAI,EAAE;YACR,2CAA2C;YAC3C,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAG;gBAChF,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;aAClE;iBAAM;gBACL,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;aACtB;SACF;KACF;IAED,mCAAmC;IACnC,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC,CAAA;IAE3D,yCAAyC;IACzC,0DAA0D;IAC1D,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE;QAC9C,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC,CAAA;KACnC;IAED,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE;QACpB,IAAI,IAAI,GAAG,qBAAqB,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC,CAAA;QAE9D,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE;YACxB,IAAI,GAAG,qBAAqB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;SACzC;QAED,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAA;KACvB;SAAM;QACL,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;KAC3B;AACH,CAAC;AAxCD,4CAwCC"}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,493 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const t = require("babel-types");
const babel_generator_1 = require("babel-generator");
const code_frame_1 = require("@babel/code-frame");
const constant_1 = require("./constant");
const lodash_1 = require("lodash");
const fs = require("fs");
const path = require("path");
const jsx_1 = require("./jsx");
const adapter_1 = require("./adapter");
const options_1 = require("./options");
const template = require('babel-template');
exports.incrementId = () => {
let id = 0;
return () => id++;
};
function getSuperClassCode(path) {
const superClass = path.node.superClass;
if (t.isIdentifier(superClass)) {
const binding = path.scope.getBinding(superClass.name);
if (binding && binding.kind === 'module') {
const bindingPath = binding.path.parentPath;
if (bindingPath.isImportDeclaration()) {
const source = bindingPath.node.source;
if (source.value === constant_1.TARO_PACKAGE_NAME) {
return;
}
try {
const p = pathResolver(source.value, options_1.transformOptions.sourcePath) + (options_1.transformOptions.isTyped ? '.tsx' : '.js');
const code = fs.readFileSync(p, 'utf8');
return {
code,
sourcePath: source.value
};
}
catch (error) {
return;
}
}
}
}
}
exports.getSuperClassCode = getSuperClassCode;
function isContainStopPropagation(path) {
let matched = false;
if (path) {
path.traverse({
Identifier(p) {
if (p.node.name === 'stopPropagation' &&
p.parentPath.parentPath.isCallExpression()) {
matched = true;
}
}
});
}
return matched;
}
exports.isContainStopPropagation = isContainStopPropagation;
function decodeUnicode(s) {
return unescape(s.replace(/\\(u[0-9a-fA-F]{4})/gm, '%$1'));
}
exports.decodeUnicode = decodeUnicode;
function isVarName(str) {
if (typeof str !== 'string') {
return false;
}
if (str.trim() !== str) {
return false;
}
try {
// tslint:disable-next-line:no-unused-expression
new Function(str, 'var ' + str);
}
catch (e) {
return false;
}
return true;
}
exports.isVarName = isVarName;
function findMethodName(expression) {
let methodName;
if (t.isIdentifier(expression) ||
t.isJSXIdentifier(expression)) {
methodName = expression.name;
}
else if (t.isStringLiteral(expression)) {
methodName = expression.value;
}
else if (t.isMemberExpression(expression) &&
t.isIdentifier(expression.property)) {
const { code } = babel_generator_1.default(expression);
const ids = code.split('.');
if (ids[0] === 'this' && ids[1] === 'props' && ids[2]) {
methodName = code.replace('this.props.', '');
}
else {
methodName = expression.property.name;
}
}
else if (t.isCallExpression(expression) &&
t.isMemberExpression(expression.callee) &&
t.isIdentifier(expression.callee.object)) {
methodName = expression.callee.object.name;
}
else if (t.isCallExpression(expression) &&
t.isMemberExpression(expression.callee) &&
t.isMemberExpression(expression.callee.object) &&
t.isIdentifier(expression.callee.property) &&
expression.callee.property.name === 'bind' &&
t.isIdentifier(expression.callee.object.property)) {
methodName = expression.callee.object.property.name;
}
else {
throw codeFrameError(expression.loc, '当 props 为事件时(props name 以 `on` 开头),只能传入一个 this 作用域下的函数。');
}
return methodName;
}
exports.findMethodName = findMethodName;
function setParentCondition(jsx, expr, array = false) {
const conditionExpr = jsx.findParent(p => p.isConditionalExpression());
const logicExpr = jsx.findParent(p => p.isLogicalExpression({ operator: '&&' }));
if (array) {
const ifAttrSet = new Set([
adapter_1.Adapter.if,
adapter_1.Adapter.else
]);
const logicalJSX = jsx.findParent(p => p.isJSXElement() && p.node.openingElement.attributes.some(a => ifAttrSet.has(a.name.name)));
if (logicalJSX) {
const attr = logicalJSX.node.openingElement.attributes.find(a => ifAttrSet.has(a.name.name));
if (attr) {
if (attr.name.name === adapter_1.Adapter.else) {
const prevElement = logicalJSX.getPrevSibling();
if (prevElement && prevElement.isJSXElement()) {
const attr = prevElement.node.openingElement.attributes.find(a => a.name.name === adapter_1.Adapter.if);
if (attr && t.isJSXExpressionContainer(attr.value)) {
expr = t.conditionalExpression(reverseBoolean(lodash_1.cloneDeep(attr.value.expression)), expr, t.arrayExpression());
return expr;
}
}
}
else if (t.isJSXExpressionContainer(attr.value)) {
expr = t.conditionalExpression(lodash_1.cloneDeep(attr.value.expression), expr, t.arrayExpression());
return expr;
}
}
}
}
if (conditionExpr && conditionExpr.isConditionalExpression()) {
const consequent = conditionExpr.get('consequent');
if (consequent === jsx || jsx.findParent(p => p === consequent)) {
expr = t.conditionalExpression(lodash_1.cloneDeep(conditionExpr.get('test').node), expr, array ? t.arrayExpression([]) : t.nullLiteral());
}
}
if (logicExpr && logicExpr.isLogicalExpression({ operator: '&&' })) {
const consequent = logicExpr.get('right');
if (consequent === jsx || jsx.findParent(p => p === consequent)) {
expr = t.conditionalExpression(lodash_1.cloneDeep(logicExpr.get('left').node), expr, array ? t.arrayExpression([]) : t.nullLiteral());
}
}
return expr;
}
exports.setParentCondition = setParentCondition;
function generateAnonymousState(scope, expression, refIds, isLogical) {
let variableName = `anonymousState_${scope.generateUid()}`;
let statementParent = expression.getStatementParent();
if (!statementParent) {
throw codeFrameError(expression.node.loc, '无法生成匿名 State尝试先把值赋到一个变量上再把变量调换。');
}
const jsx = isLogical ? expression : expression.findParent(p => p.isJSXElement());
const callExpr = jsx.findParent(p => p.isCallExpression() && isArrayMapCallExpression(p));
const ifExpr = jsx.findParent(p => p.isIfStatement());
const blockStatement = jsx.findParent(p => p.isBlockStatement() && p.parentPath === ifExpr);
const expr = setParentCondition(jsx, lodash_1.cloneDeep(expression.node));
if (!callExpr) {
refIds.add(t.identifier(variableName));
statementParent.insertBefore(buildConstVariableDeclaration(variableName, expr));
if (blockStatement && blockStatement.isBlockStatement()) {
blockStatement.traverse({
VariableDeclarator: (p) => {
const { id, init } = p.node;
if (t.isIdentifier(id) && !id.name.startsWith(constant_1.LOOP_STATE)) {
const newId = scope.generateDeclaredUidIdentifier('$' + id.name);
refIds.forEach((refId) => {
if (refId.name === variableName && !variableName.startsWith('_$')) {
refIds.delete(refId);
}
});
variableName = newId.name;
refIds.add(t.identifier(variableName));
blockStatement.scope.rename(id.name, newId.name);
p.parentPath.replaceWith(template('ID = INIT;')({ ID: newId, INIT: init }));
}
}
});
}
}
else {
variableName = `${constant_1.LOOP_STATE}_${callExpr.scope.generateUid()}`;
const func = callExpr.node.arguments[0];
if (t.isArrowFunctionExpression(func)) {
if (!t.isBlockStatement(func.body)) {
func.body = t.blockStatement([
buildConstVariableDeclaration(variableName, expr),
t.returnStatement(func.body)
]);
}
else {
func.body.body.splice(func.body.body.length - 1, 0, buildConstVariableDeclaration(variableName, expr));
}
}
}
const id = t.identifier(variableName);
expression.replaceWith(id);
return id;
}
exports.generateAnonymousState = generateAnonymousState;
function isArrayMapCallExpression(callExpression) {
return callExpression &&
t.isCallExpression(callExpression.node) &&
t.isMemberExpression(callExpression.node.callee) &&
t.isIdentifier(callExpression.node.callee.property, { name: 'map' });
}
exports.isArrayMapCallExpression = isArrayMapCallExpression;
function buildConstVariableDeclaration(variableName, expresion) {
return t.variableDeclaration('const', [
t.variableDeclarator(t.identifier(variableName), expresion)
]);
}
exports.buildConstVariableDeclaration = buildConstVariableDeclaration;
function setTemplate(name, path, templates) {
const parentPath = path.parentPath;
const jsxChildren = parentPath.findParent(p => p.isJSXElement());
if (name && !jsxChildren) {
templates.set(name, path.node);
}
}
exports.setTemplate = setTemplate;
function isContainFunction(p) {
let bool = false;
p.traverse({
CallExpression() {
bool = true;
}
});
return bool;
}
exports.isContainFunction = isContainFunction;
function slash(input) {
const isExtendedLengthPath = /^\\\\\?\\/.test(input);
const hasNonAscii = /[^\u0000-\u0080]+/.test(input);
const hasChinese = /[^\u4e00-\u9fa5]+/.test(input); // has Chinese characters
if (isExtendedLengthPath || (hasNonAscii && !hasChinese)) {
return input;
}
return input.replace(/\\/g, '/');
}
function pathResolver(source, location) {
const extName = path.extname(source);
const promotedPath = source;
if (!['js', 'tsx'].includes(extName)) {
try {
const pathExist = fs.existsSync(path.resolve(path.dirname(location), source, 'index.js'));
const tsxPathExist = fs.existsSync(path.resolve(path.dirname(location), source, 'index.tsx'));
if (pathExist || tsxPathExist) {
let p = path.join(promotedPath, 'index');
if (!p.startsWith('.')) {
p = './' + p;
}
return slash(p);
}
return slash(promotedPath);
}
catch (error) {
return slash(promotedPath);
}
}
return slash(promotedPath.split('.').slice(0, -1).join('.'));
}
exports.pathResolver = pathResolver;
function codeFrameError(node, msg) {
let errMsg = '';
try {
errMsg = code_frame_1.codeFrameColumns(exports.setting.sourceCode, node && node.type && node.loc ? node.loc : node, {
highlightCode: true
});
}
catch (error) {
errMsg = 'failed to locate source';
}
return new Error(`${msg}
-----
${errMsg}`);
}
exports.codeFrameError = codeFrameError;
exports.setting = {
sourceCode: ''
};
function createUUID() {
return '$' + 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
let r = Math.random() * 16 | 0;
let v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
}).replace(/-/g, '').slice(0, 8);
}
exports.createUUID = createUUID;
function createRandomLetters(n) {
const str = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
return Array(n).join().split(',').map(function () { return str.charAt(Math.floor(Math.random() * str.length)); }).join('');
}
exports.createRandomLetters = createRandomLetters;
function isBlockIfStatement(ifStatement, blockStatement) {
return ifStatement && blockStatement &&
ifStatement.isIfStatement() &&
blockStatement.isBlockStatement();
}
exports.isBlockIfStatement = isBlockIfStatement;
function buildCodeFrame(code) {
return (loc) => code_frame_1.codeFrameColumns(code, loc);
}
exports.buildCodeFrame = buildCodeFrame;
function isNumeric(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
}
exports.isNumeric = isNumeric;
function buildJSXAttr(name, value) {
return t.jSXAttribute(t.jSXIdentifier(name), t.jSXExpressionContainer(value));
}
exports.buildJSXAttr = buildJSXAttr;
function newJSXIfAttr(jsx, value, path) {
const element = jsx.openingElement;
if (!t.isJSXIdentifier(element.name)) {
return;
}
if (element.name.name === 'Block' || element.name.name === 'block' || !path) {
element.attributes.push(buildJSXAttr(adapter_1.Adapter.if, value));
}
else {
const block = jsx_1.buildBlockElement();
newJSXIfAttr(block, value);
block.children.push(jsx);
path.node = block;
}
}
exports.newJSXIfAttr = newJSXIfAttr;
function getSlotName(name) {
return name.slice(6).toLowerCase();
}
exports.getSlotName = getSlotName;
function isContainJSXElement(path) {
let matched = false;
path.traverse({
JSXElement(p) {
matched = true;
p.stop();
}
});
return matched;
}
exports.isContainJSXElement = isContainJSXElement;
function hasComplexExpression(path) {
let matched = false;
if (isContainJSXElement(path)) {
return false;
}
if (path.isObjectExpression()) {
return true;
}
if (path.isTemplateLiteral() || path.isCallExpression()) {
return true;
}
if (path.isArrayExpression()) {
const { elements } = path.node;
if (elements.some(el => t.isObjectExpression(el) || t.isArrayExpression(el))) {
return true;
}
}
path.traverse({
CallExpression: (p) => {
matched = true;
p.stop();
},
TemplateLiteral(p) {
matched = true;
p.stop();
},
ObjectExpression(p) {
matched = true;
p.stop();
},
ArrayExpression(p) {
const { elements } = p.node;
if (elements.some(el => t.isObjectExpression(el))) {
return true;
}
},
TaggedTemplateExpression(p) {
matched = true;
p.stop();
},
MemberExpression(path) {
const jsxElement = path.findParent(p => p.isJSXExpressionContainer());
const object = path.get('object');
const property = path.get('property');
const parentPath = path.parentPath;
if (jsxElement &&
object.isThisExpression() &&
//不应该替换成 data导致 wxml 里的 this.data.a[1] 转换失败
property.isIdentifier({ name: 'state' }) &&
parentPath.isMemberExpression() &&
parentPath.parentPath.isMemberExpression()) {
const sourceCode = parentPath.parentPath.getSource();
if (sourceCode.includes('[') && sourceCode.includes(']')) {
matched = true;
path.stop();
}
}
}
});
return matched;
}
exports.hasComplexExpression = hasComplexExpression;
function findFirstIdentifierFromMemberExpression(node, member) {
let id;
let object = node.object;
while (true) {
if (t.identifier(object) && !t.isMemberExpression(object)) {
id = object;
if (member) {
object = member;
}
break;
}
object = object.object;
}
return id;
}
exports.findFirstIdentifierFromMemberExpression = findFirstIdentifierFromMemberExpression;
function getArgumentName(arg) {
if (t.isThisExpression(arg)) {
return 'this';
}
else if (t.isNullLiteral(arg)) {
return 'null';
}
else if (t.isStringLiteral(arg) || t.isNumericLiteral(arg)) {
return arg.value;
}
else if (t.isIdentifier(arg)) {
return arg.name;
}
else {
return babel_generator_1.default(arg).code;
}
throw new Error(`bind 不支持传入该参数: ${arg}`);
}
exports.getArgumentName = getArgumentName;
function isAllLiteral(...args) {
return args.every(p => t.isLiteral(p));
}
exports.isAllLiteral = isAllLiteral;
function reverseBoolean(expression) {
return t.unaryExpression('!', expression);
}
exports.reverseBoolean = reverseBoolean;
function isEmptyDeclarator(node) {
if (t.isVariableDeclarator(node) &&
(node.init === null ||
t.isNullLiteral(node.init))) {
return true;
}
return false;
}
exports.isEmptyDeclarator = isEmptyDeclarator;
function toLetters(num) {
let mod = num % 26;
let pow = num / 26 | 0;
let out = mod ? String.fromCharCode(64 + mod) : (--pow, 'Z');
const letter = pow ? toLetters(pow) + out : out;
return letter.toLowerCase();
}
exports.toLetters = toLetters;
function findIdentifierFromStatement(statement) {
if (t.isVariableDeclaration(statement)) {
const declarator = statement.declarations.find(s => t.isIdentifier(s.id));
if (declarator && t.isIdentifier(declarator.id)) {
return declarator.id.name;
}
}
return '__return';
}
exports.findIdentifierFromStatement = findIdentifierFromStatement;
//# sourceMappingURL=utils.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,93 @@
{
"name": "@tarojs/transformer-wx",
"version": "1.2.13",
"description": "Transfrom Nerv Component to Wechat mini program.",
"repository": {
"type": "git",
"url": "git+https://github.com/NervJS/taro.git"
},
"main": "index.js",
"files": [
"index.js",
"lib",
"cli.js",
"dist"
],
"scripts": {
"test:cov": "jest --coverage && npm run lint",
"test": "jest",
"dev": "tsc -w --pretty",
"lint": "tslint",
"build": "tsc"
},
"author": "O2Team",
"license": "MIT",
"jest": {
"testEnvironment": "node",
"transform": {
"^.+\\.tsx?$": "ts-jest"
},
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
"moduleFileExtensions": [
"ts",
"tsx",
"js",
"jsx",
"json",
"node"
],
"testPathIgnorePatterns": [
"node_modules",
"utils"
]
},
"dependencies": {
"@babel/code-frame": "^7.0.0-beta.44",
"babel-core": "^6.26.3",
"babel-eslint": "^8.2.3",
"babel-helper-evaluate-path": "^0.5.0",
"babel-helper-mark-eval-scopes": "^0.4.3",
"babel-helper-remove-or-void": "^0.4.3",
"babel-plugin-danger-remove-unused-import": "^1.1.1",
"babel-plugin-minify-dead-code": "^0.5.2",
"babel-plugin-remove-dead-code": "^1.3.2",
"babel-plugin-syntax-dynamic-import": "^6.18.0",
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-plugin-transform-define": "^1.3.0",
"babel-plugin-transform-es2015-template-literals": "^6.22.0",
"babel-plugin-transform-flow-strip-types": "^6.22.0",
"babel-traverse": "^6.26.0",
"babel-types": "^6.26.0",
"eslint": "^4.15.0",
"eslint-plugin-taro": "1.2.13",
"html": "^1.0.0",
"lodash": "^4.17.5",
"prettier": "^1.14.2",
"typescript": "^3.2.2"
},
"devDependencies": {
"@tarojs/taro": "1.2.13",
"@types/babel-core": "^6.25.5",
"@types/babel-generator": "^6.25.1",
"@types/babel-template": "^6.25.0",
"@types/babel-traverse": "6.25.3",
"@types/babel-types": "^6.25.2",
"@types/eslint": "^4.16.5",
"@types/jest": "^22.2.3",
"@types/lodash": "^4.14.105",
"@types/node": "^9.6.2",
"jest": "^23.0.1",
"jest-cli": "^22.1.4",
"ts-jest": "^22.4.6",
"tslint": "^5.10.0",
"tslint-config-prettier": "^1.10.0",
"tslint-config-standard": "^7.0.0"
},
"publishConfig": {
"access": "public"
},
"bugs": {
"url": "https://github.com/NervJS/taro/issues"
},
"homepage": "https://github.com/NervJS/taro#readme"
}

View File

@ -3,6 +3,10 @@ import { WeElement, define } from 'omi'
define('my-element', class extends WeElement {
a = 1
data = {
arr: ['arr test']
}
clickHandler = ()=> {
console.log('clicked'+ this.a)
}
@ -11,7 +15,10 @@ define('my-element', class extends WeElement {
return (
<view>
<text onClick={this.clickHandler}>Hello, {this.props.msg}!</text>
<view>
{this.data.arr[0]}
</view>
</view>
)
}
})
})

View File

@ -1,6 +1,7 @@
import { h } from './h'
import { define } from './define'
import { updateData } from './update-data'
class Component {
constructor() { }
@ -9,7 +10,10 @@ class Component {
this.beforeUpdate && this.beforeUpdate()
this.beforeRender && this.beforeRender()
if (patch) {
this.data = Object.assign(this.data || {}, patch)
this.data = this.data || {}
Object.keys(patch).forEach(path =>{
updateData(this.data, path, patch[path])
})
}
this._weappRef.setData(this.data)
this.updated && this.updated()
@ -127,7 +131,7 @@ root.create = {
if(ins.onReachBottom){
config.onReachBottom = ins.onReachBottom.bind(ins)
}
if(ins.onPullDownRefresh){
config.onPullDownRefresh = ins.onPullDownRefresh.bind(ins)
}

View File

@ -0,0 +1,11 @@
export function updateData(origin, path, value) {
const arr = path.replace(/]/g, '').replace(/\[/g, '.').split('.')
let current = origin
for (let i = 0, len = arr.length; i < len; i++) {
if (i === len - 1) {
current[arr[i]] = value
} else {
current = current[arr[i]]
}
}
}