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

feat(ssr): support rpx

上级 b2105d80
...@@ -33,7 +33,7 @@ function proxy(target, track, trigger) { ...@@ -33,7 +33,7 @@ function proxy(target, track, trigger) {
const globalData = {}; const globalData = {};
const ssrServerRef = (value, key, shallow = false) => { const ssrServerRef = (value, key, shallow = false) => {
assertKey(key, shallow); assertKey(key, shallow);
const ctx = vue.useSSRContext(); const ctx = vue.getCurrentInstance() && vue.useSSRContext();
let state; let state;
if (ctx) { if (ctx) {
const __uniSSR = ctx[UNI_SSR] || (ctx[UNI_SSR] = {}); const __uniSSR = ctx[UNI_SSR] || (ctx[UNI_SSR] = {});
......
...@@ -76,7 +76,7 @@ const globalData: Record<string, any> = {} ...@@ -76,7 +76,7 @@ const globalData: Record<string, any> = {}
const ssrServerRef: SSRRef = (value, key, shallow = false) => { const ssrServerRef: SSRRef = (value, key, shallow = false) => {
assertKey(key, shallow) assertKey(key, shallow)
const ctx = useSSRContext() const ctx = getCurrentInstance() && useSSRContext()
let state: Record<string, any> let state: Record<string, any>
if (ctx) { if (ctx) {
const __uniSSR = ctx[UNI_SSR] || (ctx[UNI_SSR] = {}) const __uniSSR = ctx[UNI_SSR] || (ctx[UNI_SSR] = {})
......
import fs from 'fs' import fs from 'fs'
import path from 'path' import path from 'path'
import { once } from '@dcloudio/uni-shared' import { extend } from '@vue/shared'
import { once, defaultRpx2Unit } from '@dcloudio/uni-shared'
import { parseJson } from './json' import { parseJson } from './json'
...@@ -12,3 +13,8 @@ export const parseManifestJson = (inputDir: string) => { ...@@ -12,3 +13,8 @@ export const parseManifestJson = (inputDir: string) => {
} }
export const parseManifestJsonOnce = once(parseManifestJson) export const parseManifestJsonOnce = once(parseManifestJson)
export const parseRpx2UnitOnce = once((inputDir: string) => {
const { h5 } = parseManifestJsonOnce(inputDir)
return extend({}, defaultRpx2Unit, (h5 && h5.rpx) || {})
})
import { createRpx2Unit } from '../../src/dom/style'
const defaultRpx2Unit = {
unit: 'rem',
unitRatio: 10 / 320,
unitPrecision: 5,
}
const { unit, unitRatio, unitPrecision } = defaultRpx2Unit
describe('Rpx2Unit', () => {
test('rem', () => {
const rpx2unit = createRpx2Unit(unit, unitRatio, unitPrecision)
expect(rpx2unit('10')).toBe('10')
expect(rpx2unit('10rpx')).toBe('0.3125rem')
expect(rpx2unit('10upx')).toBe('0.3125rem')
expect(rpx2unit('color:red;height:10rpx;width:100px')).toBe(
'color:red;height:0.3125rem;width:100px'
)
})
})
...@@ -4,6 +4,27 @@ Object.defineProperty(exports, '__esModule', { value: true }); ...@@ -4,6 +4,27 @@ Object.defineProperty(exports, '__esModule', { value: true });
var shared = require('@vue/shared'); var shared = require('@vue/shared');
const unitRE = new RegExp(`"[^"]+"|'[^']+'|url\\([^)]+\\)|(\\d*\\.?\\d+)[r|u]px`, 'g');
function toFixed(number, precision) {
const multiplier = Math.pow(10, precision + 1);
const wholeNumber = Math.floor(number * multiplier);
return (Math.round(wholeNumber / 10) * 10) / multiplier;
}
const defaultRpx2Unit = {
unit: 'rem',
unitRatio: 10 / 320,
unitPrecision: 5,
};
function createRpx2Unit(unit, unitRatio, unitPrecision) {
return (val) => val.replace(unitRE, (m, $1) => {
if (!$1) {
return m;
}
const value = toFixed(parseFloat($1) * unitRatio, unitPrecision);
return value === 0 ? '0' : `${value}${unit}`;
});
}
function passive(passive) { function passive(passive) {
return { passive }; return { passive };
} }
...@@ -324,9 +345,11 @@ exports.UNI_SSR_DATA = UNI_SSR_DATA; ...@@ -324,9 +345,11 @@ exports.UNI_SSR_DATA = UNI_SSR_DATA;
exports.UNI_SSR_GLOBAL_DATA = UNI_SSR_GLOBAL_DATA; exports.UNI_SSR_GLOBAL_DATA = UNI_SSR_GLOBAL_DATA;
exports.UNI_SSR_STORE = UNI_SSR_STORE; exports.UNI_SSR_STORE = UNI_SSR_STORE;
exports.addFont = addFont; exports.addFont = addFont;
exports.createRpx2Unit = createRpx2Unit;
exports.debounce = debounce; exports.debounce = debounce;
exports.decode = decode; exports.decode = decode;
exports.decodedQuery = decodedQuery; exports.decodedQuery = decodedQuery;
exports.defaultRpx2Unit = defaultRpx2Unit;
exports.getEnvLocale = getEnvLocale; exports.getEnvLocale = getEnvLocale;
exports.getLen = getLen; exports.getLen = getLen;
exports.invokeArrayFns = invokeArrayFns; exports.invokeArrayFns = invokeArrayFns;
......
...@@ -10,6 +10,8 @@ export declare const COMPONENT_PREFIX: string; ...@@ -10,6 +10,8 @@ export declare const COMPONENT_PREFIX: string;
export declare const COMPONENT_SELECTOR_PREFIX = "uni-"; export declare const COMPONENT_SELECTOR_PREFIX = "uni-";
export declare function createRpx2Unit(unit: string, unitRatio: number, unitPrecision: number): (val: string) => string;
export declare function debounce(fn: Function, delay: number): { export declare function debounce(fn: Function, delay: number): {
(this: any): void; (this: any): void;
cancel(): void; cancel(): void;
...@@ -26,6 +28,12 @@ export declare function decode(text: string | number): string; ...@@ -26,6 +28,12 @@ export declare function decode(text: string | number): string;
export declare function decodedQuery(query?: Record<string, any>): Record<string, string>; export declare function decodedQuery(query?: Record<string, any>): Record<string, string>;
export declare const defaultRpx2Unit: {
unit: string;
unitRatio: number;
unitPrecision: number;
};
export declare function getEnvLocale(): string; export declare function getEnvLocale(): string;
export declare function getLen(str?: string): number; export declare function getLen(str?: string): number;
...@@ -76,6 +84,8 @@ export declare function removeLeadingSlash(str: string): string; ...@@ -76,6 +84,8 @@ export declare function removeLeadingSlash(str: string): string;
export declare const RESPONSIVE_MIN_WIDTH = 768; export declare const RESPONSIVE_MIN_WIDTH = 768;
export declare type Rpx2UnitOptions = typeof defaultRpx2Unit;
export declare const sanitise: (val: unknown) => any; export declare const sanitise: (val: unknown) => any;
declare function scrollTo_2(scrollTop: number | string, duration: number): void; declare function scrollTo_2(scrollTop: number | string, duration: number): void;
......
import { isString, isHTMLTag, isSVGTag, isPlainObject, isArray } from '@vue/shared'; import { isString, isHTMLTag, isSVGTag, isPlainObject, isArray } from '@vue/shared';
const unitRE = new RegExp(`"[^"]+"|'[^']+'|url\\([^)]+\\)|(\\d*\\.?\\d+)[r|u]px`, 'g');
function toFixed(number, precision) {
const multiplier = Math.pow(10, precision + 1);
const wholeNumber = Math.floor(number * multiplier);
return (Math.round(wholeNumber / 10) * 10) / multiplier;
}
const defaultRpx2Unit = {
unit: 'rem',
unitRatio: 10 / 320,
unitPrecision: 5,
};
function createRpx2Unit(unit, unitRatio, unitPrecision) {
return (val) => val.replace(unitRE, (m, $1) => {
if (!$1) {
return m;
}
const value = toFixed(parseFloat($1) * unitRatio, unitPrecision);
return value === 0 ? '0' : `${value}${unit}`;
});
}
function passive(passive) { function passive(passive) {
return { passive }; return { passive };
} }
...@@ -304,4 +325,4 @@ function getEnvLocale() { ...@@ -304,4 +325,4 @@ function getEnvLocale() {
return (lang && lang.replace(/[.:].*/, '')) || 'en'; return (lang && lang.replace(/[.:].*/, '')) || 'en';
} }
export { BUILT_IN_TAGS, COMPONENT_NAME_PREFIX, COMPONENT_PREFIX, COMPONENT_SELECTOR_PREFIX, NAVBAR_HEIGHT, ON_REACH_BOTTOM_DISTANCE, PLUS_RE, PRIMARY_COLOR, RESPONSIVE_MIN_WIDTH, TABBAR_HEIGHT, TAGS, UNI_SSR, UNI_SSR_DATA, UNI_SSR_GLOBAL_DATA, UNI_SSR_STORE, addFont, debounce, decode, decodedQuery, getEnvLocale, getLen, invokeArrayFns, isBuiltInComponent, isCustomElement, isNativeTag, normalizeDataset, normalizeTarget, once, parseQuery, passive, plusReady, removeLeadingSlash, sanitise, scrollTo, stringifyQuery, updateElementStyle }; export { BUILT_IN_TAGS, COMPONENT_NAME_PREFIX, COMPONENT_PREFIX, COMPONENT_SELECTOR_PREFIX, NAVBAR_HEIGHT, ON_REACH_BOTTOM_DISTANCE, PLUS_RE, PRIMARY_COLOR, RESPONSIVE_MIN_WIDTH, TABBAR_HEIGHT, TAGS, UNI_SSR, UNI_SSR_DATA, UNI_SSR_GLOBAL_DATA, UNI_SSR_STORE, addFont, createRpx2Unit, debounce, decode, decodedQuery, defaultRpx2Unit, getEnvLocale, getLen, invokeArrayFns, isBuiltInComponent, isCustomElement, isNativeTag, normalizeDataset, normalizeTarget, once, parseQuery, passive, plusReady, removeLeadingSlash, sanitise, scrollTo, stringifyQuery, updateElementStyle };
import { FontFaceDescriptors } from 'css-font-loading-module' import { FontFaceDescriptors } from 'css-font-loading-module'
import { isString } from '@vue/shared' import { isString } from '@vue/shared'
export * from './style'
export function passive(passive: boolean) { export function passive(passive: boolean) {
return { passive } return { passive }
} }
......
const unitRE = new RegExp(
`"[^"]+"|'[^']+'|url\\([^)]+\\)|(\\d*\\.?\\d+)[r|u]px`,
'g'
)
function toFixed(number: number, precision: number) {
const multiplier = Math.pow(10, precision + 1)
const wholeNumber = Math.floor(number * multiplier)
return (Math.round(wholeNumber / 10) * 10) / multiplier
}
export const defaultRpx2Unit = {
unit: 'rem',
unitRatio: 10 / 320,
unitPrecision: 5,
}
export type Rpx2UnitOptions = typeof defaultRpx2Unit
export function createRpx2Unit(
unit: string,
unitRatio: number,
unitPrecision: number
) {
return (val: string) =>
val.replace(unitRE, (m, $1) => {
if (!$1) {
return m
}
const value = toFixed(parseFloat($1) * unitRatio, unitPrecision)
return value === 0 ? '0' : `${value}${unit}`
})
}
const context = (() => {
if (typeof globalThis !== 'undefined') {
return globalThis
} else if (typeof self !== 'undefined') {
return self
} else if (typeof window !== 'undefined') {
return window
} else {
return Function('return this')()
}
})()
// assign defines
const defines = __DEFINES__ const defines = __DEFINES__
Object.keys(defines).forEach((key) => { Object.keys(defines).forEach((key) => {
const segments = key.split('.') const segments = key.split('.')
let target = context let target = globalThis
for (let i = 0; i < segments.length; i++) { for (let i = 0; i < segments.length; i++) {
const segment = segments[i] const segment = segments[i]
if (i === segments.length - 1) { if (i === segments.length - 1) {
...@@ -23,3 +11,14 @@ Object.keys(defines).forEach((key) => { ...@@ -23,3 +11,14 @@ Object.keys(defines).forEach((key) => {
} }
} }
}) })
const { createRpx2Unit } = require('@dcloudio/uni-shared')
const rpx2unit = createRpx2Unit(__UNIT__, __UNIT_RATIO__, __UNIT_PRECISION__)
const shared = require('@vue/shared')
const oldStringifyStyle = shared.stringifyStyle
shared.stringifyStyle = (styles) => rpx2unit(oldStringifyStyle(styles))
const serverRender = require('@vue/server-renderer')
const oldSsrRenderStyle = serverRender.ssrRenderStyle
serverRender.ssrRenderStyle = (raw) =>
shared.isString(raw)
? rpx2unit(oldSsrRenderStyle(raw))
: oldSsrRenderStyle(raw)
...@@ -2,6 +2,8 @@ import path from 'path' ...@@ -2,6 +2,8 @@ import path from 'path'
import fs from 'fs-extra' import fs from 'fs-extra'
import { UserConfig } from 'vite' import { UserConfig } from 'vite'
import autoprefixer from 'autoprefixer' import autoprefixer from 'autoprefixer'
import { extend } from '@vue/shared'
import { parseRpx2UnitOnce } from '@dcloudio/uni-cli-shared'
import { VitePluginUniResolvedOptions } from '..' import { VitePluginUniResolvedOptions } from '..'
import { uniapp } from '../utils' import { uniapp } from '../utils'
...@@ -19,7 +21,12 @@ export function createCss( ...@@ -19,7 +21,12 @@ export function createCss(
return { return {
postcss: { postcss: {
plugins: [ plugins: [
uniapp({ page: options.platform === 'h5' ? 'uni-page-body' : 'body' }), uniapp(
extend(
{ page: options.platform === 'h5' ? 'uni-page-body' : 'body' },
parseRpx2UnitOnce(options.inputDir)
)
),
autoprefixer(), autoprefixer(),
], ],
}, },
......
...@@ -11,7 +11,7 @@ export function createDefine( ...@@ -11,7 +11,7 @@ export function createDefine(
{ server }: UserConfig, { server }: UserConfig,
{ command }: ConfigEnv { command }: ConfigEnv
): UserConfig['define'] { ): UserConfig['define'] {
const features = initFeatures({ return initFeatures({
inputDir, inputDir,
command, command,
platform, platform,
...@@ -19,8 +19,4 @@ export function createDefine( ...@@ -19,8 +19,4 @@ export function createDefine(
manifestJson: parseManifestJsonOnce(inputDir), manifestJson: parseManifestJsonOnce(inputDir),
ssr: !!(server && server.middlewareMode), ssr: !!(server && server.middlewareMode),
}) })
if (server && server.middlewareMode) {
Object.assign(globalThis, features)
}
return features
} }
import { ResolvedConfig } from 'vite' import { ResolvedConfig } from 'vite'
import { parserOptions } from '@vue/compiler-dom' import { rewriteSsrNativeTag, rewriteSsrRenderStyle } from '../utils'
import { isNativeTag } from '@dcloudio/uni-shared'
// import alias from 'module-alias' // import alias from 'module-alias'
export function initConfig(config: ResolvedConfig) { export function initConfig(config: ResolvedConfig) {
if (config.server.middlewareMode) { if (
// TODO compiler-ssr时,传入的 isNativeTag 会被 @vue/compiler-dom 的 isNativeTag 覆盖 (config.command === 'serve' && config.server.middlewareMode) ||
// https://github.com/vuejs/vue-next/blob/master/packages/compiler-ssr/src/index.ts#L36 (config.command === 'build' && config.build.ssr)
parserOptions.isNativeTag = isNativeTag ) {
rewriteSsrNativeTag()
rewriteSsrRenderStyle(process.env.UNI_INPUT_DIR)
} }
// let ssr = (config as any).ssr as SSROptions // let ssr = (config as any).ssr as SSROptions
// if (!ssr) { // if (!ssr) {
......
...@@ -140,7 +140,7 @@ export function initPlugins( ...@@ -140,7 +140,7 @@ export function initPlugins(
addPlugin( addPlugin(
plugins, plugins,
uniSSRPlugin(extend({ exclude: [...COMMON_EXCLUDE] }, options)), uniSSRPlugin(config, extend({ exclude: [...COMMON_EXCLUDE] }, options)),
'vite:vue' 'vite:vue'
) )
......
...@@ -11,7 +11,12 @@ export function uniMainJsPlugin( ...@@ -11,7 +11,12 @@ export function uniMainJsPlugin(
const mainJsPath = mainPath + '.js' const mainJsPath = mainPath + '.js'
const mainTsPath = mainPath + '.ts' const mainTsPath = mainPath + '.ts'
const pagesJsonJsPath = slash(path.resolve(options.inputDir, 'pages.json.js')) const pagesJsonJsPath = slash(path.resolve(options.inputDir, 'pages.json.js'))
const isSSR = config.server.middlewareMode const isSSR =
config.command === 'serve'
? !!config.server.middlewareMode
: config.command === 'build'
? !!(config.build.ssr || config.build.ssrManifest)
: false
return { return {
name: 'vite:uni-main-js', name: 'vite:uni-main-js',
transform(code, id, ssr) { transform(code, id, ssr) {
......
...@@ -33,13 +33,9 @@ export function uniPagesJsonPlugin( ...@@ -33,13 +33,9 @@ export function uniPagesJsonPlugin(
if (id.endsWith(PAGES_JSON_JS)) { if (id.endsWith(PAGES_JSON_JS)) {
return { return {
code: code:
(config.define!.__UNI_FEATURE_RPX__ (options.command === 'serve' || (options.command === 'build' && ssr)
? registerGlobalCode(ssr) ? registerGlobalCode(config, ssr)
: '') + : '') + generatePagesJsonCode(ssr, code, config, options),
(options.command === 'serve'
? registerDevServerGlobalCode(ssr)
: '') +
generatePagesJsonCode(ssr, code, config, options),
map: { mappings: '' }, map: { mappings: '' },
} }
} }
...@@ -100,15 +96,15 @@ function getGlobal(ssr?: boolean) { ...@@ -100,15 +96,15 @@ function getGlobal(ssr?: boolean) {
return ssr ? 'global' : 'window' return ssr ? 'global' : 'window'
} }
function registerGlobalCode(ssr?: boolean) { function registerGlobalCode(config: ResolvedConfig, ssr?: boolean) {
const name = getGlobal(ssr) const name = getGlobal(ssr)
return `import {upx2px} from '@dcloudio/uni-h5' const rpx2pxCode = config.define!.__UNI_FEATURE_RPX__
${name}.rpx2px = upx2px ? `import {upx2px} from '@dcloudio/uni-h5'
${name}.rpx2px = upx2px
` `
} : ''
function registerDevServerGlobalCode(ssr?: boolean) { return `${rpx2pxCode}
const name = getGlobal(ssr) import {uni,getCurrentPages,getApp,UniServiceJSBridge,UniViewJSBridge} from '@dcloudio/uni-h5'
return `import {uni,getCurrentPages,getApp,UniServiceJSBridge,UniViewJSBridge} from '@dcloudio/uni-h5'
${name}.getApp = getApp ${name}.getApp = getApp
${name}.getCurrentPages = getCurrentPages ${name}.getCurrentPages = getCurrentPages
${name}.uni = uni ${name}.uni = uni
......
import path from 'path' import path from 'path'
import debug from 'debug' import debug from 'debug'
import crypto from 'crypto' import crypto from 'crypto'
import { Plugin } from 'vite' import { Plugin, ResolvedConfig } from 'vite'
import { walk } from 'estree-walker' import { walk } from 'estree-walker'
import { CallExpression } from 'estree' import { CallExpression } from 'estree'
import { OutputChunk } from 'rollup'
import { createFilter } from '@rollup/pluginutils' import { createFilter } from '@rollup/pluginutils'
import { MagicString } from '@vue/compiler-sfc' import { MagicString } from '@vue/compiler-sfc'
import { parseRpx2UnitOnce } from '@dcloudio/uni-cli-shared'
import { UniPluginFilterOptions } from '.' import { UniPluginFilterOptions } from '.'
import { import {
isIdentifier, isIdentifier,
isCallExpression, isCallExpression,
isMemberExpression, isMemberExpression,
generateSSRDefineCode,
generateSSREntryServerCode, generateSSREntryServerCode,
} from '../../utils' } from '../../utils'
...@@ -21,7 +26,10 @@ const KEYED_FUNC_RE = /(ssrRef|shallowSsrRef)/ ...@@ -21,7 +26,10 @@ const KEYED_FUNC_RE = /(ssrRef|shallowSsrRef)/
const ENTRY_SERVER_JS = 'entry-server.js' const ENTRY_SERVER_JS = 'entry-server.js'
export function uniSSRPlugin(options: UniPluginFilterOptions): Plugin { export function uniSSRPlugin(
config: ResolvedConfig,
options: UniPluginFilterOptions
): Plugin {
const filter = createFilter(options.include, options.exclude) const filter = createFilter(options.include, options.exclude)
const entryServerJs = path.join(options.inputDir, ENTRY_SERVER_JS) const entryServerJs = path.join(options.inputDir, ENTRY_SERVER_JS)
const entryServerJsCode = generateSSREntryServerCode() const entryServerJsCode = generateSSREntryServerCode()
...@@ -73,6 +81,18 @@ export function uniSSRPlugin(options: UniPluginFilterOptions): Plugin { ...@@ -73,6 +81,18 @@ export function uniSSRPlugin(options: UniPluginFilterOptions): Plugin {
map: s.generateMap().toString(), map: s.generateMap().toString(),
} }
}, },
generateBundle(_options, bundle) {
const chunk = bundle['entry-server.js'] as OutputChunk
if (chunk) {
chunk.code =
generateSSRDefineCode(
config.define!,
parseRpx2UnitOnce(options.inputDir)
) +
'\n' +
chunk.code
}
},
} }
} }
......
import fs from 'fs' import fs from 'fs'
import path from 'path' import path from 'path'
import { ConfigEnv } from 'vite' import { ConfigEnv } from 'vite'
import { isArray } from '@vue/shared' import { extend, isArray } from '@vue/shared'
interface ProjectFeatures {} interface ProjectFeatures {}
interface PagesFeatures { interface PagesFeatures {
...@@ -245,7 +245,7 @@ export function initFeatures(options: InitFeaturesOptions) { ...@@ -245,7 +245,7 @@ export function initFeatures(options: InitFeaturesOptions) {
initPagesFeature(options), initPagesFeature(options),
initProjectFeature(options) initProjectFeature(options)
) )
return { const features = {
__UNI_FEATURE_WX__: wx, // 是否启用小程序的组件实例 API,如:selectComponent 等(uni-core/src/service/plugin/appConfig) __UNI_FEATURE_WX__: wx, // 是否启用小程序的组件实例 API,如:selectComponent 等(uni-core/src/service/plugin/appConfig)
__UNI_FEATURE_WXS__: wxs, // 是否启用 wxs 支持,如:getComponentDescriptor 等(uni-core/src/view/plugin/appConfig) __UNI_FEATURE_WXS__: wxs, // 是否启用 wxs 支持,如:getComponentDescriptor 等(uni-core/src/view/plugin/appConfig)
__UNI_FEATURE_RPX__: rpx, // 是否启用运行时 rpx 支持 __UNI_FEATURE_RPX__: rpx, // 是否启用运行时 rpx 支持
...@@ -272,4 +272,9 @@ export function initFeatures(options: InitFeaturesOptions) { ...@@ -272,4 +272,9 @@ export function initFeatures(options: InitFeaturesOptions) {
__UNI_FEATURE_NAVIGATIONBAR_SEARCHINPUT__: navigationBarSearchInput, // 是否启用标题栏搜索框 __UNI_FEATURE_NAVIGATIONBAR_SEARCHINPUT__: navigationBarSearchInput, // 是否启用标题栏搜索框
__UNI_FEATURE_NAVIGATIONBAR_TRANSPARENT__: navigationBarTransparent, // 是否启用透明标题栏 __UNI_FEATURE_NAVIGATIONBAR_TRANSPARENT__: navigationBarTransparent, // 是否启用透明标题栏
} }
// ssr nodejs features
if (options.ssr) {
extend(globalThis, features)
}
return features
} }
...@@ -2,6 +2,8 @@ import { extend } from '@vue/shared' ...@@ -2,6 +2,8 @@ import { extend } from '@vue/shared'
import { rule, Rule, Declaration, Plugin } from 'postcss' import { rule, Rule, Declaration, Plugin } from 'postcss'
import selectorParser from 'postcss-selector-parser' import selectorParser from 'postcss-selector-parser'
import { import {
createRpx2Unit,
defaultRpx2Unit,
isBuiltInComponent, isBuiltInComponent,
COMPONENT_SELECTOR_PREFIX, COMPONENT_SELECTOR_PREFIX,
} from '@dcloudio/uni-shared' } from '@dcloudio/uni-shared'
...@@ -13,12 +15,12 @@ interface UniAppCssProcessorOptions { ...@@ -13,12 +15,12 @@ interface UniAppCssProcessorOptions {
unitPrecision?: number // 单位精度,默认5 unitPrecision?: number // 单位精度,默认5
} }
const defaultUniAppCssProcessorOptions = { const defaultUniAppCssProcessorOptions = extend(
page: 'body', {
unit: 'rem', page: 'body',
unitRatio: 10 / 320, },
unitPrecision: 5, defaultRpx2Unit
} )
const BG_PROPS = [ const BG_PROPS = [
'background', 'background',
...@@ -34,7 +36,7 @@ const BG_PROPS = [ ...@@ -34,7 +36,7 @@ const BG_PROPS = [
function transform( function transform(
selector: selectorParser.Node, selector: selectorParser.Node,
opts: UniAppCssProcessorOptions, page: string,
state: { bg: boolean } state: { bg: boolean }
) { ) {
if (selector.type !== 'tag') { if (selector.type !== 'tag') {
...@@ -44,7 +46,6 @@ function transform( ...@@ -44,7 +46,6 @@ function transform(
if (isBuiltInComponent(value)) { if (isBuiltInComponent(value)) {
selector.value = COMPONENT_SELECTOR_PREFIX + value selector.value = COMPONENT_SELECTOR_PREFIX + value
} else if (value === 'page') { } else if (value === 'page') {
const { page } = opts
if (!page) { if (!page) {
return return
} }
...@@ -67,52 +68,40 @@ function createBodyBackgroundRule(origRule: Rule) { ...@@ -67,52 +68,40 @@ function createBodyBackgroundRule(origRule: Rule) {
} }
} }
function walkRules(opts: UniAppCssProcessorOptions) { function walkRules(page: string) {
return (rule: Rule) => { return (rule: Rule) => {
const state = { bg: false } const state = { bg: false }
rule.selector = selectorParser((selectors) => rule.selector = selectorParser((selectors) =>
selectors.walk((selector) => transform(selector, opts, state)) selectors.walk((selector) => transform(selector, page, state))
).processSync(rule.selector) ).processSync(rule.selector)
state.bg && createBodyBackgroundRule(rule) state.bg && createBodyBackgroundRule(rule)
} }
} }
const unitRE = new RegExp( function walkDecls(rpx2unit: ReturnType<typeof createRpx2Unit>) {
`"[^"]+"|'[^']+'|url\\([^)]+\\)|(\\d*\\.?\\d+)[r|u]px`,
'g'
)
function toFixed(number: number, precision: number) {
const multiplier = Math.pow(10, precision + 1)
const wholeNumber = Math.floor(number * multiplier)
return (Math.round(wholeNumber / 10) * 10) / multiplier
}
function walkDecls(opts: Required<UniAppCssProcessorOptions>) {
return (decl: Declaration) => { return (decl: Declaration) => {
const { value } = decl const { value } = decl
if (value.indexOf('rpx') === -1 && value.indexOf('upx') === -1) { if (value.indexOf('rpx') === -1 && value.indexOf('upx') === -1) {
return return
} }
decl.value = decl.value.replace(unitRE, (m, $1) => { decl.value = rpx2unit(decl.value)
if (!$1) {
return m
}
const value = toFixed(parseFloat($1) * opts.unitRatio, opts.unitPrecision)
return value === 0 ? '0' : `${value}${opts.unit}`
})
} }
} }
export const uniapp = (opts?: UniAppCssProcessorOptions) => { export const uniapp = (opts?: UniAppCssProcessorOptions) => {
const options = extend({}, defaultUniAppCssProcessorOptions, opts || {}) const { page, unit, unitRatio, unitPrecision } = extend(
{},
defaultUniAppCssProcessorOptions,
opts || {}
)
const rpx2unit = createRpx2Unit(unit, unitRatio, unitPrecision)
return { return {
postcssPlugin: 'uni-app', postcssPlugin: 'uni-app',
prepare() { prepare() {
return { return {
OnceExit(root) { OnceExit(root) {
root.walkDecls(walkDecls(options)) root.walkDecls(walkDecls(rpx2unit))
root.walkRules(walkRules(options)) root.walkRules(walkRules(page))
}, },
} }
}, },
......
import path from 'path' import path from 'path'
import fs from 'fs-extra' import fs from 'fs-extra'
import { isString, NormalizedStyle } from '@vue/shared'
import {
isNativeTag,
createRpx2Unit,
Rpx2UnitOptions,
} from '@dcloudio/uni-shared'
import { parseRpx2UnitOnce } from '@dcloudio/uni-cli-shared'
function serializeDefine(define: Record<string, any>): string { function serializeDefine(define: Record<string, any>): string {
let res = `{` let res = `{`
for (const key in define) { for (const key in define) {
const val = define[key] const val = define[key]
res += `${JSON.stringify(key)}: ${ res += `${JSON.stringify(key)}: ${
typeof val === 'string' ? `(${val})` : JSON.stringify(val) typeof val === 'string' && !key.startsWith('process.env.') // process.env.* 必须序列化为字符串
? `(${val})`
: JSON.stringify(val)
}, ` }, `
} }
return res + `}` return res + `}`
} }
export function generateSSREnvCode(define: Record<string, any>): string { export function generateSSRDefineCode(
define: Record<string, any>,
{ unit, unitRatio, unitPrecision }: Rpx2UnitOptions
): string {
return fs return fs
.readFileSync(path.join(__dirname, '../../lib/ssr/env.js'), 'utf8') .readFileSync(path.join(__dirname, '../../lib/ssr/define.js'), 'utf8')
.replace('__DEFINES__', serializeDefine(define)) .replace('__DEFINES__', serializeDefine(define))
.replace('__UNIT__', JSON.stringify(unit))
.replace('__UNIT_RATIO__', JSON.stringify(unitRatio))
.replace('__UNIT_PRECISION__', JSON.stringify(unitPrecision))
} }
export function generateSSREntryServerCode() { export function generateSSREntryServerCode() {
...@@ -24,3 +39,24 @@ export function generateSSREntryServerCode() { ...@@ -24,3 +39,24 @@ export function generateSSREntryServerCode() {
'utf8' 'utf8'
) )
} }
export function rewriteSsrNativeTag() {
const { parserOptions } = require('@vue/compiler-dom')
// TODO compiler-ssr时,传入的 isNativeTag 会被 @vue/compiler-dom 的 isNativeTag 覆盖
// https://github.com/vuejs/vue-next/blob/master/packages/compiler-ssr/src/index.ts#L36
parserOptions.isNativeTag = isNativeTag
}
export function rewriteSsrRenderStyle(inputDir: string) {
const { unit, unitRatio, unitPrecision } = parseRpx2UnitOnce(inputDir)
const rpx2unit = createRpx2Unit(unit, unitRatio, unitPrecision)
const shared = require('@vue/shared')
const oldStringifyStyle = shared.stringifyStyle
shared.stringifyStyle = (styles: NormalizedStyle | undefined) =>
rpx2unit(oldStringifyStyle(styles))
const serverRender = require('@vue/server-renderer')
const oldSsrRenderStyle = serverRender.ssrRenderStyle
// 仅对字符串类型做转换,非字符串类型,通过 stringifyStyle 转换
serverRender.ssrRenderStyle = (raw: unknown) =>
isString(raw) ? rpx2unit(oldSsrRenderStyle(raw)) : oldSsrRenderStyle(raw)
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册