Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add compiler macros defineAppConfig and definePageConfig #10269

Closed
wants to merge 10 commits into from
1 change: 1 addition & 0 deletions packages/taro-cli/templates/default/global.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/// <reference path="node_modules/@tarojs/plugin-platform-weapp/types/shims-weapp.d.ts" />
/// <reference path="node_modules/@tarojs/taro/types/index.d.ts" />

declare module '*.png';
declare module '*.gif';
Expand Down
42 changes: 42 additions & 0 deletions packages/taro-helper/src/babelRegister.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,45 @@
import * as path from 'path'
import type { PluginItem, NodePath } from '@babel/core'

/**
* Inject `defineAppConfig` and `definePageConfig`
* require header at the top of a config file,
* without the need to specifically require them
* if they are used
*/
function injectDefineConfigHeader (babel: any): PluginItem {
const appConfig = 'const { defineAppConfig } = require("@tarojs/helper")'
const pageConfig = 'const { definePageConfig } = require("@tarojs/helper")'

const prependHeader = (nodePath: NodePath<any>, header: string) => {
const parsedHeader = babel.parse(header, { filename: '' }).program.body[0]
nodePath.node.body.unshift(parsedHeader)
}

const enterHandler = (nodePath: NodePath<any>) => {
const { scope, node } = nodePath

scope.traverse(node, {
CallExpression (p) {
const callee = p.node.callee
// @ts-ignore
switch (callee.name) {
case 'defineAppConfig':
prependHeader(nodePath, appConfig)
return
case 'definePageConfig':
prependHeader(nodePath, pageConfig)
}
}
})
}

return {
visitor: {
Program: { enter: enterHandler }
}
}
}

export default function createBabelRegister ({ only }) {
require('@babel/register')({
Expand All @@ -8,6 +49,7 @@ export default function createBabelRegister ({ only }) {
require.resolve('@babel/preset-typescript')
],
plugins: [
injectDefineConfigHeader,
[require.resolve('@babel/plugin-proposal-decorators'), {
legacy: true
}],
Expand Down
146 changes: 141 additions & 5 deletions packages/taro-helper/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import {
} from './constants'
import createBabelRegister from './babelRegister'

import type { PluginItem, NodePath } from '@babel/core'

const execSync = child_process.execSync

export function normalizePath (path: string) {
Expand Down Expand Up @@ -242,7 +244,7 @@ export function resolveScriptPath (p: string): string {
}

export function generateEnvList (env: Record<string, any>): Record<string, any> {
const res = { }
const res = {}
if (env && !isEmptyObject(env)) {
for (const key in env) {
try {
Expand All @@ -256,7 +258,7 @@ export function generateEnvList (env: Record<string, any>): Record<string, any>
}

export function generateConstantsList (constants: Record<string, any>): Record<string, any> {
const res = { }
const res = {}
if (constants && !isEmptyObject(constants)) {
for (const key in constants) {
if (isPlainObject(constants[key])) {
Expand Down Expand Up @@ -334,7 +336,7 @@ export function getInstalledNpmPkgVersion (pkgName: string, basedir: string): st
return fs.readJSONSync(pkgPath).version
}

export const recursiveMerge = <T = any>(src: Partial<T>, ...args: (Partial<T> | undefined)[]) => {
export const recursiveMerge = <T = any> (src: Partial<T>, ...args: (Partial<T> | undefined)[]) => {
return mergeWith(src, ...args, (value, srcValue) => {
const typeValue = typeof value
const typeSrcValue = typeof srcValue
Expand Down Expand Up @@ -423,7 +425,7 @@ export function unzip (zipPath) {
fileNameArr.shift()
const fileName = fileNameArr.join('/')
const writeStream = fs.createWriteStream(path.join(path.dirname(zipPath), fileName))
writeStream.on('close', () => {})
writeStream.on('close', () => { })
readStream
.pipe(filter)
.pipe(writeStream)
Expand Down Expand Up @@ -491,14 +493,148 @@ export function removeHeadSlash (str: string) {
return str.replace(/^(\/|\\)/, '')
}

export function defineAppConfig (config: any) {
return config
}

export function definePageConfig (config: any) {
return config
}

// converts ast nodes to js object
function exprToObject (node: any) {
const types = ['BooleanLiteral', 'StringLiteral', 'NumericLiteral']

if (types.includes(node.type)) {
return node.value
}

if (node.name === 'undefined' && !node.value) {
return undefined
}

if (node.type === 'NullLiteral') {
return null
}

if (node.type === 'ObjectExpression') {
return genProps(node.properties)
}

if (node.type === 'ArrayExpression') {
return node.elements.reduce((acc: any, el: any) => [
...acc,
...(
el!.type === 'SpreadElement'
? exprToObject(el.argument)
: [exprToObject(el)]
)
], [])
}
}

// converts ObjectExpressions to js object
function genProps (props: any[]) {
return props.reduce((acc, prop) => {
if (prop.type === 'SpreadElement') {
return {
...acc,
...exprToObject(prop.argument)
}
} else if (prop.type !== 'ObjectMethod') {
const v = exprToObject(prop.value)
if (v !== undefined) {
return {
...acc,
[prop.key.name || prop.key.value]: v
}
}
}
return acc
}, {})
}

// read page config from a sfc file instead of the regular config file
function readSFCPageConfig (configPath: string) {
if (!fs.existsSync(configPath)) return {}

const sfcSource = fs.readFileSync(configPath, 'utf8')
const dpcReg = /definePageConfig\(\{[\w\W]+?\}\)/g
const matches = sfcSource.match(dpcReg)

let result: any = {}

if (matches && matches.length === 1) {
const callExprHandler = (p: any) => {
const { callee } = p.node
if (!callee.name) return
if (callee.name && callee.name !== 'definePageConfig') return

const configNode = p.node.arguments[0]
result = exprToObject(configNode)
p.stop()
}

const configSource = matches[0]
const babel = require('@babel/core')
const ast = babel.parse(configSource, { filename: '' })

babel.traverse(ast.program, { CallExpression: callExprHandler })
}

return result
}

export function readConfig (configPath: string) {
let result: any = {}
if (fs.existsSync(configPath)) {
createBabelRegister({
only: [configPath]
only: [
configPath,
// support importing other config files using es module syntax
// but the imported file shall be kept within the src directory
function (filepath: string) {
return configPath.split('src')[0] === filepath.split('src')[0]
}
]
})
delete require.cache[configPath]
result = getModuleDefaultExport(require(configPath))
} else {
const extNames = ['.js', '.jsx', '.ts', '.tsx', '.vue']

// check source file extension
for (const ext of extNames) {
const tempPath = configPath.replace('.config', ext)
if (fs.existsSync(tempPath)) {
configPath = tempPath
break
}
}

result = readSFCPageConfig(configPath)
}

return result
}

// A babel-plugin to remove the PageConfig defined in sfc file.
// This plugin must be used in a post babel-loader.
export function pluginRemovePageConfig (babel: any): PluginItem {
const { types: t } = babel

return {
name: 'plugin:remove_pageconfig',
visitor: {
CallExpression (nodePath: NodePath<any>, state) {
if (!/src/.test(state.filename)) return
if (/index\.config\.(t|j)sx?$/.test(state.filename)) return

const { callee } = nodePath.node
if (!t.isIdentifier(callee, { name: 'definePageConfig' })) return

nodePath.remove()
}
}
}
}
3 changes: 3 additions & 0 deletions packages/taro-helper/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ declare interface helper {
getModuleDefaultExport: (exports: any) => any;
removeHeadSlash: (str: string) => string;
readConfig: (configPath: string) => any;
defineAppConfig (config: any): any;
definePageConfig (config: any): any;
pluginRemovePageConfig (babel: any): any;
PLATFORMS: any;
processTypeEnum: typeof processTypeEnum;
processTypeMap: IProcessTypeMap;
Expand Down
3 changes: 3 additions & 0 deletions packages/taro-helper/types/utils.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,6 @@ export declare function addPlatforms(platform: string): void;
export declare const getModuleDefaultExport: (exports: any) => any;
export declare const removeHeadSlash: (str: string) => string;
export declare const readConfig: (configPath: string) => any;
export declare function defineAppConfig (config: any): any;
export declare function definePageConfig (config: any): any;
export declare function pluginRemovePageConfig (babel: any): any;
3 changes: 2 additions & 1 deletion packages/taro-loader/src/h5.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { getOptions, stringifyRequest } from 'loader-utils'
import { AppConfig } from '@tarojs/taro'
import { join, dirname } from 'path'
import { frameworkMeta } from './utils'
import { readConfig } from '@tarojs/helper'

function genResource (path: string, pages: Map<string, string>, loaderContext: webpack.loader.LoaderContext) {
const stringify = (s: string): string => stringifyRequest(loaderContext, s)
Expand All @@ -12,7 +13,7 @@ function genResource (path: string, pages: Map<string, string>, loaderContext: w
load: function() {
return import(${stringify(join(loaderContext.context, path))})
}
}, require(${stringify(pages.get(path)!)}).default || {}),
}, ${JSON.stringify(readConfig(pages.get(path)!))}),
`
}

Expand Down
13 changes: 13 additions & 0 deletions packages/taro-mini-runner/src/__tests__/compiler-macros.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { compile, getOutput } from './utils/compiler'

describe('compiler macros - defineAppConfig and definePageConfig', () => {
test('should read app and page configs', async () => {
const { stats, config } = await compile('compiler-macros')
const assets = stats.toJson().assets || []

expect(assets.length).toMatchSnapshot()

const output = getOutput(stats, config)
expect(output).toMatchSnapshot()
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// babel-preset-taro 更多选项和默认值:
// https://github.com/NervJS/taro/blob/next/packages/babel-preset-taro/README.md
module.exports = {
plugins: ['@babel/plugin-proposal-do-expressions'],
presets: [
['taro', {
framework: 'react',
ts: false,
reactJsxRuntime: 'classic'
}]
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export default defineAppConfig({
pages: [
'pages/index/index'
],
window: {
backgroundTextStyle: 'light',
navigationBarBackgroundColor: '#fff',
navigationBarTitleText: 'WeChat',
navigationBarTextStyle: 'black'
}
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import React, { Component } from 'react'
import './app.css'

class App extends Component {
componentDidMount () {}

componentDidShow () {}

componentDidHide () {}

componentDidCatchError () {}

// this.props.children 是将要会渲染的页面
render () {
return this.props.children
}
}

export default App
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<meta content="width=device-width,initial-scale=1,user-scalable=no" name="viewport">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-touch-fullscreen" content="yes">
<meta name="format-detection" content="telephone=no,address=no">
<meta name="apple-mobile-web-app-status-bar-style" content="white">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" >
<title></title>
<script>
!function(x){function w(){var v,u,t,tes,s=x.document,r=s.documentElement,a=r.getBoundingClientRect().width;if(!v&&!u){var n=!!x.navigator.appVersion.match(/AppleWebKit.*Mobile.*/);v=x.devicePixelRatio;tes=x.devicePixelRatio;v=n?v:1,u=1/v}if(a>=640){r.style.fontSize="40px"}else{if(a<=320){r.style.fontSize="20px"}else{r.style.fontSize=a/320*20+"px"}}}x.addEventListener("resize",function(){w()});w()}(window);
</script>
</head>
<body>
<div id="app"></div>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React, { Component } from 'react'
import { View, Text } from '@tarojs/components'
import './index.css'

definePageConfig({
navigationBarTitleText: '首页'
})

export default class Index extends Component {
render() {
return (
<View className='index'>
<Text>Hello world!</Text>
</View>
)
}
}
Loading