提交 0114e258 编写于 作者: fxy060608's avatar fxy060608

feat(app): console

上级 95517b9b
import path from 'path'
import { import {
initProvide, initProvide,
uniViteInjectPlugin, uniViteInjectPlugin,
uniCssScopedPlugin, uniCssScopedPlugin,
getAppStyleIsolation, getAppStyleIsolation,
parseManifestJsonOnce, parseManifestJsonOnce,
uniConsolePlugin,
} from '@dcloudio/uni-cli-shared' } from '@dcloudio/uni-cli-shared'
import { UniAppPlugin } from './plugin' import { UniAppPlugin } from './plugin'
import { uniTemplatePlugin } from './plugins/template' import { uniTemplatePlugin } from './plugins/template'
...@@ -31,6 +33,15 @@ function initUniCssScopedPluginOptions() { ...@@ -31,6 +33,15 @@ function initUniCssScopedPluginOptions() {
const plugins = [ const plugins = [
// uniResolveIdPlugin(), // uniResolveIdPlugin(),
uniConsolePlugin({
filename(filename) {
filename = path.relative(process.env.UNI_INPUT_DIR, filename)
if (filename.startsWith('.')) {
return ''
}
return filename
},
}),
uniMainJsPlugin(), uniMainJsPlugin(),
uniManifestJsonPlugin(), uniManifestJsonPlugin(),
uniPagesJsonPlugin(), uniPagesJsonPlugin(),
......
import * as Types from '@babel/types'
import { PluginObj, PluginPass } from '@babel/core'
import { normalizePath } from '@dcloudio/uni-cli-shared'
const METHODS = ['error', 'warn', 'info', 'log', 'debug']
const FORMAT_LOG = '__f__'
module.exports = function ({
types: t,
}: {
types: typeof Types
}): PluginObj<
{ opts: { filename?: (filename: string) => string | undefined } } & PluginPass
> {
return {
visitor: {
CallExpression(path, state) {
let {
opts,
file: {
opts: { filename },
},
} = state
if (!filename) {
return
}
if (opts && opts.filename) {
filename = opts.filename(filename)
}
if (!filename) {
return
}
const { callee, arguments: args, loc } = path.node
if (!t.isMemberExpression(callee)) {
return
}
const { object, property } = callee
if (!t.isIdentifier(object) || !t.isIdentifier(property)) {
return
}
if (object.name !== 'console' || !METHODS.includes(property.name)) {
return
}
if (property.name === 'debug') {
property.name = 'log'
}
const arg = args[0]
if (
arg &&
t.isCallExpression(arg) &&
t.isIdentifier(arg.callee) &&
arg.callee.name === FORMAT_LOG
) {
return
}
args.unshift({
type: 'StringLiteral',
value: ` at ${normalizePath(filename)}:${loc!.start.line}`,
} as Types.StringLiteral)
args.unshift(t.stringLiteral(property.name))
path.replaceWith(t.callExpression(t.identifier(FORMAT_LOG), args))
},
},
}
}
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`console console.debug 1`] = `"__f__('info','at foo.vue:1',a,b,c);"`;
exports[`console console.error 1`] = `"__f__('info','at foo.vue:1',a,b,c);"`;
exports[`console console.info 1`] = `"__f__('info','at foo.vue:1',a,b,c);"`;
exports[`console console.log 1`] = `"const a = 1;__f__('log','at foo.vue:1',a);"`;
exports[`console console.log multiline 1`] = `
"const a = 1;
__f__('log','at foo.vue:3',a);
const b = 2
__f__('log','at foo.vue:5',a,b);
__f__('log','at foo.vue:6',a,b,c);
"
`;
exports[`console console.warn 1`] = `"__f__('info','at foo.vue:1',a,b,c);"`;
import { rewriteConsoleExpr } from '../src/logs/console'
import { normalizeLog } from '../src/hbx/formatLog'
const filename = 'foo.vue'
describe('console', () => {
test('console.log', () => {
expect(
rewriteConsoleExpr(filename, `const a = 1;console.log(a);`)
).toMatchSnapshot()
})
test('console.log multiline', () => {
expect(
rewriteConsoleExpr(
filename,
`const a = 1;
console.log(a);
const b = 2
console.log(a,b);
console.log(a,b,c);
`
)
).toMatchSnapshot()
})
test('console.info', () => {
expect(
rewriteConsoleExpr(filename, `console.info(a,b,c);`)
).toMatchSnapshot()
})
test('console.debug', () => {
expect(
rewriteConsoleExpr(filename, `console.info(a,b,c);`)
).toMatchSnapshot()
})
test('console.warn', () => {
expect(
rewriteConsoleExpr(filename, `console.info(a,b,c);`)
).toMatchSnapshot()
})
test('console.error', () => {
expect(
rewriteConsoleExpr(filename, `console.info(a,b,c);`)
).toMatchSnapshot()
})
test('console.log format', () => {
expect(
normalizeLog('log', 'at ' + filename + ':1', ['a', 'b', { a: 1 }])
).toBe(
`a---COMMA---b---COMMA------BEGIN:JSON---{"a":1}---END:JSON--- at foo.vue:1`
)
})
})
...@@ -3,6 +3,7 @@ const libDir = path.resolve(__dirname, '../lib') ...@@ -3,6 +3,7 @@ const libDir = path.resolve(__dirname, '../lib')
export function initProvide() { export function initProvide() {
const cryptoDefine = [path.join(libDir, 'crypto.js'), 'default'] const cryptoDefine = [path.join(libDir, 'crypto.js'), 'default']
return { return {
__f__: [path.join(__dirname, '../hbx/formatLog.js'), 'formatLog'],
crypto: cryptoDefine, crypto: cryptoDefine,
'window.crypto': cryptoDefine, 'window.crypto': cryptoDefine,
'global.crypto': cryptoDefine, 'global.crypto': cryptoDefine,
......
function typof(v: unknown) { import { toTypeString, toRawType } from '@vue/shared'
var s = Object.prototype.toString.call(v)
return s.substring(8, s.length - 1)
}
function isDebugMode() { function isDebugMode() {
// @ts-expect-error // @ts-expect-error
return typeof __channelId__ === 'string' && __channelId__ return typeof __channelId__ === 'string' && __channelId__
} }
function jsonStringifyReplacer(key: string, value: unknown) { function jsonStringifyReplacer(k: string, p: unknown) {
switch (typof(value)) { switch (toRawType(p)) {
case 'Function': case 'Function':
return 'function() { [native code] }' return 'function() { [native code] }'
default: default:
return value return p
} }
} }
type LogType = 'log' | 'info' | 'warn' | 'error' export function normalizeLog(
type: 'log' | 'info' | 'debug' | 'warn' | 'error',
// export function log(type: LogType) { filename: string,
// for ( args: unknown[]
// var _len = arguments.length, ) {
// args = new Array(_len > 1 ? _len - 1 : 0),
// _key = 1;
// _key < _len;
// _key++
// ) {
// args[_key - 1] = arguments[_key]
// }
// console[type].apply(console, args)
// }
export default function formatLog() {
for (
var _len = arguments.length, args = new Array(_len), _key = 0;
_key < _len;
_key++
) {
args[_key] = arguments[_key]
}
var type = args.shift() as LogType
if (isDebugMode()) { if (isDebugMode()) {
args.push(args.pop().replace('at ', 'uni-app:///')) args.push(filename.replace('at ', 'uni-app:///'))
return console[type].apply(console, args) return console[type].apply(console, args)
} }
var msgs = args.map(function (v) { const msgs = args.map(function (v) {
var type = Object.prototype.toString.call(v).toLowerCase() const type = toTypeString(v).toLowerCase()
if (type === '[object object]' || type === '[object array]') { if (type === '[object object]' || type === '[object array]') {
try { try {
v = v =
...@@ -64,8 +41,7 @@ export default function formatLog() { ...@@ -64,8 +41,7 @@ export default function formatLog() {
} else if (v === undefined) { } else if (v === undefined) {
v = '---UNDEFINED---' v = '---UNDEFINED---'
} else { } else {
var vType = typof(v).toUpperCase() const vType = toRawType(v).toUpperCase()
if (vType === 'NUMBER' || vType === 'BOOLEAN') { if (vType === 'NUMBER' || vType === 'BOOLEAN') {
v = '---BEGIN:' + vType + '---' + v + '---END:' + vType + '---' v = '---BEGIN:' + vType + '---' + v + '---END:' + vType + '---'
} else { } else {
...@@ -73,23 +49,15 @@ export default function formatLog() { ...@@ -73,23 +49,15 @@ export default function formatLog() {
} }
} }
} }
return v return v
}) })
var msg = '' return msgs.join('---COMMA---') + ' ' + filename
}
if (msgs.length > 1) {
var lastMsg = msgs.pop()
msg = msgs.join('---COMMA---')
if (lastMsg.indexOf(' at ') === 0) {
msg += lastMsg
} else {
msg += '---COMMA---' + lastMsg
}
} else {
msg = msgs[0]
}
console[type](msg) export function formatLog(
type: 'log' | 'info' | 'debug' | 'warn' | 'error',
filename: string,
...args: unknown[]
) {
console[type](normalizeLog(type, filename, args))
} }
import { MagicString } from '@vue/compiler-sfc'
const F = '__f__'
export function rewriteConsoleExpr(filename: string, code: string) {
const re = /(console\.(log|info|debug|warn|error))\(([^)]+)\)/g
const locate = getLocator(code)
const s = new MagicString(code)
let match: RegExpExecArray | null
while ((match = re.exec(code))) {
const [, expr, type] = match
s.overwrite(
match.index,
match.index + expr.length + 1,
F + `('${type}','at ${filename}:${locate(match.index).line + 1}',`
)
}
return s.toString()
}
function getLocator(source: string) {
const originalLines = source.split('\n')
const lineOffsets: number[] = []
for (let i = 0, pos = 0; i < originalLines.length; i++) {
lineOffsets.push(pos)
pos += originalLines[i].length + 1
}
return function locate(index: number) {
let i = 0
let j = lineOffsets.length
while (i < j) {
const m = (i + j) >> 1
if (index < lineOffsets[m]) {
j = m
} else {
i = m + 1
}
}
const line = i - 1
const column = index - lineOffsets[line]
return { line, column }
}
}
import debug from 'debug'
import { Plugin } from 'vite' import { Plugin } from 'vite'
import { createFilter, FilterPattern } from '@rollup/pluginutils' import { createFilter, FilterPattern } from '@rollup/pluginutils'
import { isJsFile, parseVueRequest } from '../utils'
import { rewriteConsoleExpr } from '../../logs/console'
export interface ConsoleOptions { export interface ConsoleOptions {
filename?: (filename: string) => string
include?: FilterPattern include?: FilterPattern
exclude?: FilterPattern exclude?: FilterPattern
} }
const debugConsole = debug('vite:uni:console')
export function uniConsolePlugin(options: ConsoleOptions): Plugin { export function uniConsolePlugin(options: ConsoleOptions): Plugin {
const filter = createFilter(options.include, options.exclude) const filter = createFilter(options.include, options.exclude)
return { return {
name: 'vite:uni-app-console', name: 'vite:uni-app-console',
enforce: 'pre',
apply: 'build',
transform(code, id) { transform(code, id) {
if (!filter(id)) return null if (!filter(id)) return null
if (!isJsFile(id)) return null
let { filename } = parseVueRequest(id)
if (options.filename) {
filename = options.filename(filename)
}
if (!filename) {
return null
}
if (!code.includes('console.')) { if (!code.includes('console.')) {
return null return null
} }
debugConsole(id)
return {
code: rewriteConsoleExpr(filename, code),
map: null,
}
}, },
} }
} }
...@@ -4,6 +4,7 @@ export * from './copy' ...@@ -4,6 +4,7 @@ export * from './copy'
export * from './inject' export * from './inject'
export * from './mainJs' export * from './mainJs'
export * from './jsonJs' export * from './jsonJs'
export * from './console'
export { assetPlugin } from './vitejs/plugins/asset' export { assetPlugin } from './vitejs/plugins/asset'
export { cssPlugin, cssPostPlugin } from './vitejs/plugins/css' export { cssPlugin, cssPostPlugin } from './vitejs/plugins/css'
import path, { sep } from 'path' import { sep } from 'path'
import debug from 'debug' import debug from 'debug'
import { Plugin } from 'vite' import { Plugin } from 'vite'
...@@ -16,14 +16,7 @@ import { walk } from 'estree-walker' ...@@ -16,14 +16,7 @@ import { walk } from 'estree-walker'
import { extend } from '@vue/shared' import { extend } from '@vue/shared'
import { MagicString } from '@vue/compiler-sfc' import { MagicString } from '@vue/compiler-sfc'
import { EXTNAME_JS_RE, EXTNAME_VUE } from '../../constants' import { isProperty, isReference, isMemberExpression, isJsFile } from '../utils'
import {
isProperty,
isReference,
isMemberExpression,
parseVueRequest,
} from '../utils'
interface Scope { interface Scope {
parent: Scope parent: Scope
...@@ -79,15 +72,7 @@ export function uniViteInjectPlugin(options: InjectOptions): Plugin { ...@@ -79,15 +72,7 @@ export function uniViteInjectPlugin(options: InjectOptions): Plugin {
name: 'vite:uni-inject', name: 'vite:uni-inject',
transform(code, id) { transform(code, id) {
if (!filter(id)) return null if (!filter(id)) return null
const isJs = EXTNAME_JS_RE.test(id) if (!isJsFile(id)) return null
if (!isJs) {
const { filename, query } = parseVueRequest(id)
const isVueJs =
EXTNAME_VUE.includes(path.extname(filename)) && !query.vue
if (!isVueJs) {
return null
}
}
debugInjectTry(id) debugInjectTry(id)
if (code.search(firstpass) === -1) return null if (code.search(firstpass) === -1) return null
if (sep !== '/') id = id.split(sep).join('/') if (sep !== '/') id = id.split(sep).join('/')
......
...@@ -63,6 +63,21 @@ export function createLiteral(value: string) { ...@@ -63,6 +63,21 @@ export function createLiteral(value: string) {
} as Literal } as Literal
} }
export function createIdentifier(name: string) {
return {
type: 'Identifier',
name,
} as Identifier
}
export function createCallExpression(callee: unknown, args: unknown[]) {
return {
type: 'CallExpression',
callee,
arguments: args,
} as CallExpression
}
export function parseVue(code: string, errors: SyntaxError[]) { export function parseVue(code: string, errors: SyntaxError[]) {
return parse(code, { return parse(code, {
isNativeTag: () => true, isNativeTag: () => true,
......
import path from 'path'
import qs from 'querystring' import qs from 'querystring'
import { EXTNAME_JS_RE, EXTNAME_VUE } from '../../constants'
export interface VueQuery { export interface VueQuery {
vue?: boolean vue?: boolean
...@@ -39,3 +41,16 @@ export const hashRE = /#.*$/ ...@@ -39,3 +41,16 @@ export const hashRE = /#.*$/
export const cleanUrl = (url: string) => export const cleanUrl = (url: string) =>
url.replace(hashRE, '').replace(queryRE, '') url.replace(hashRE, '').replace(queryRE, '')
export function isJsFile(id: string) {
const isJs = EXTNAME_JS_RE.test(id)
if (isJs) {
return true
}
const { filename, query } = parseVueRequest(id)
const isVueJs = EXTNAME_VUE.includes(path.extname(filename)) && !query.vue
if (isVueJs) {
return true
}
return false
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册