Skip to content

Commit 22b5810

Browse files
wip(core): enhance polyfilling and transpilation
enable polyfills global scope pollution because the code compiled by vue3-sfc-loader also require these polyfills.
1 parent 7cf123a commit 22b5810

10 files changed

+776
-1032
lines changed
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
module.exports = function basicIdentifierReplacerPlugin({ types: t }) {
2+
3+
function IdentifierVisitor(path, { opts }) {
4+
5+
if ( Object.prototype.hasOwnProperty.call(opts, path.node.name) ) {
6+
7+
const replacement = opts[path.node.name];
8+
9+
if (
10+
path.parent.type === 'UnaryExpression'
11+
&& path.parent.operator === 'typeof'
12+
&& path.parentPath.parent.type === 'BinaryExpression'
13+
//&& path.parentPath.parent.operator === '!=='
14+
&& path.parentPath.parent.right.type === 'StringLiteral'
15+
&& path.parentPath.parent.right.value === 'undefined'
16+
) {
17+
18+
//require('inspector').url() || require('inspector').open(null, null, true); debugger;
19+
20+
// typeof undefined === 'undefined'
21+
if ( replacement === 'undefined' && path.parentPath.parent.operator === '===' )
22+
return void path.parentPath.parentPath.replaceWithSourceString('true');
23+
24+
// typeof undefined !== 'undefined'
25+
if ( replacement === 'undefined' && path.parentPath.parent.operator === '!==' )
26+
return void path.parentPath.parentPath.replaceWithSourceString('false');
27+
28+
// match: typeof XXX !== 'undefined'
29+
if ( path.parentPath.parent.operator === '!==' )
30+
return void path.parentPath.parentPath.replaceWithSourceString('true');
31+
32+
// match: typeof XXX === 'undefined'
33+
if ( path.parentPath.parent.operator === '===' )
34+
return void path.parentPath.parentPath.replaceWithSourceString('false');
35+
36+
throw new Error('case not yet handled');
37+
}
38+
39+
// https://github.com/jamiebuilds/babel-handbook/blob/master/translations/en/plugin-handbook.md#replacing-a-node-with-a-source-string
40+
41+
//require('inspector').url() || require('inspector').open(null, null, true); debugger;
42+
path.replaceWithSourceString(replacement);
43+
}
44+
}
45+
46+
return {
47+
visitor: {
48+
Program: {
49+
enter(path, state) {
50+
51+
path.traverse({
52+
Identifier: IdentifierVisitor
53+
}, state);
54+
},
55+
},
56+
}
57+
};
58+
59+
}
60+

build/caniuse-isSupported.js

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// see: https://github.com/Nyalab/caniuse-api/tree/master/src
2+
3+
const memoize = require("lodash.memoize");
4+
const browserslist = require("browserslist");
5+
const {features, feature: featureUnpack} = require("caniuse-lite");
6+
const uniq = require("lodash.uniq");
7+
8+
function contains(str, substr) {
9+
return !!~str.indexOf(substr)
10+
}
11+
12+
function parseCaniuseData(feature, browsers) {
13+
var support = {}
14+
var letters
15+
var letter
16+
17+
browsers.forEach(function(browser) {
18+
support[browser] = {}
19+
for (var info in feature.stats[browser]) {
20+
letters = feature.stats[browser][info].replace(/#\d+/, "").trim().split(" ")
21+
info = parseFloat(info.split("-")[0]) //if info is a range, take the left
22+
if (isNaN(info)) continue
23+
for (var i = 0; i < letters.length ; i++) {
24+
letter = letters[i]
25+
if (letter === "d") { // skip this letter, we don't support it yet
26+
continue
27+
} else if (letter === "y"){ // min support asked, need to find the min value
28+
if (typeof support[browser][letter] === "undefined" ||
29+
info < support[browser][letter]) {
30+
support[browser][letter] = info
31+
}
32+
} else { // any other support, need to find the max value
33+
if (typeof support[browser][letter] === "undefined" ||
34+
info > support[browser][letter]) {
35+
support[browser][letter] = info
36+
}
37+
}
38+
}
39+
}
40+
})
41+
42+
return support
43+
}
44+
45+
function cleanBrowsersList(browserList) {
46+
return uniq(browserslist(browserList).map((browser) => browser.split(" ")[0]))
47+
}
48+
49+
50+
51+
52+
53+
const featuresList = Object.keys(features)
54+
55+
let browsers
56+
function setBrowserScope(browserList) {
57+
browsers = cleanBrowsersList(browserList)
58+
}
59+
60+
61+
const parse = memoize(parseCaniuseData, function(feat, browsers) {
62+
return feat.title + browsers
63+
})
64+
65+
function isSupported(feature, browsers) {
66+
let data
67+
try {
68+
data = featureUnpack(features[feature])
69+
} catch(e) {
70+
let res = find(feature)
71+
if (res.length === 1) {
72+
data = features[res[0]]
73+
} else {
74+
throw new ReferenceError(`Please provide a proper feature name. Cannot find ${feature}`)
75+
}
76+
}
77+
78+
const browserList = browserslist(browsers, {ignoreUnknownVersions: true})
79+
80+
if (browserList && browserList.length > 0) {
81+
return browserList.map((browser) => {
82+
return browser.split(" ")
83+
})
84+
.every((browser) => {
85+
return data.stats[browser[0]] &&
86+
data.stats[browser[0]][browser[1]] &&
87+
data.stats[browser[0]][browser[1]][0] === "y"
88+
})
89+
}
90+
91+
throw new ReferenceError(`browser is an unknown version: ${browsers}`)
92+
}
93+
94+
function find(query) {
95+
if (typeof query !== "string") {
96+
throw new TypeError("The `query` parameter should be a string.")
97+
}
98+
99+
if (~featuresList.indexOf(query)) { // exact match
100+
return query
101+
}
102+
103+
return featuresList.filter((file) => contains(file, query))
104+
}
105+
106+
107+
setBrowserScope()
108+
109+
module.exports = {
110+
isSupported,
111+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
module.exports = function(targetsBrowsersString) {
2+
3+
const browserslist = require('browserslist')
4+
const pluginsCompatData = require('@babel/preset-env/lib/plugins-compat-data');
5+
const filterItems = require('@babel/helper-compilation-targets').filterItems
6+
const helperCompilationTargets = require('@babel/helper-compilation-targets').default
7+
8+
const browsers = browserslist(targetsBrowsersString);
9+
10+
// see @babel/preset-env/lib/index.js:307
11+
return [...filterItems(pluginsCompatData.plugins, new Set(), new Set(), helperCompilationTargets({ browsers }), []) ];
12+
}

build/webpack.config.js

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,17 @@ const Path = require('path');
22
const zlib = require('zlib')
33
const fs = require('fs');
44

5+
const browserslist = require("browserslist");
6+
57
const Webpack = require('webpack');
68
const TerserPlugin = require('terser-webpack-plugin');
79
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
810
const DuplicatePackageCheckerPlugin = require('duplicate-package-checker-webpack-plugin');
911
const CompressionPlugin = require('compression-webpack-plugin');
12+
const basicIdentifierReplacerPlugin = require('./basicIdentifierReplacerPlugin.js');
13+
const requiredBabelPluginsNamesByBrowserTarget = require('./requiredBabelPluginsNamesByBrowserTarget.js');
14+
15+
1016
const dts = require('dts-bundle');
1117

1218
class DtsBundlePlugin {
@@ -46,7 +52,8 @@ class DtsBundlePlugin {
4652
}
4753

4854
// doc: https://github.com/Nyalab/caniuse-api#api
49-
const caniuse = require('caniuse-api')
55+
//const caniuse = require('caniuse-api')
56+
const caniuse = require('./caniuse-isSupported.js')
5057

5158
const pkg = require('../package.json');
5259

@@ -83,11 +90,23 @@ const configure = ({name, vueTarget, libraryTargetModule}) => (env = {}, { mode
8390

8491
const genSourcemap = false;
8592

93+
let actualTargetsBrowsers = targetsBrowsers;
94+
95+
// "or" / ","" -> union
96+
// "and" -> intersection
97+
// "not" -> relative complement
98+
8699
// excludes cases that make no sense
87-
const actualTargetsBrowsers = targetsBrowsers + ( libraryTargetModule ? ' and supports es6-module' : '' ) + ( vueTarget == 3 ? ' and supports proxy' : '' );
100+
actualTargetsBrowsers += ( libraryTargetModule ? ' and supports es6-module' : '' ) + ( vueTarget == 3 ? ' and supports proxy' : '' );
88101

89102
console.log('config', { actualTargetsBrowsers, noPresetEnv, noCompress, noSourceMap, genSourcemap, libraryTargetModule, vueTarget });
90103

104+
if ( browserslist(actualTargetsBrowsers).length === 0 )
105+
throw new RangeError('browserslist(' + actualTargetsBrowsers + ') selects no browsers');
106+
107+
const pluginNameList = requiredBabelPluginsNamesByBrowserTarget(actualTargetsBrowsers);
108+
const ___targetBrowserBabelPlugins = '{' + pluginNameList.map(e => `'${ e }': require('@babel/plugin-${ e }'),\n`).join('') + '}';
109+
91110
return {
92111
name,
93112

@@ -346,8 +365,8 @@ ${ pkg.name } v${ pkg.version } for vue${ vueTarget }
346365
isProd ? {
347366
test: /\.(mjs|js|cjs|ts)$/,
348367
exclude: [
349-
/core-js-pure/, // Babel should not transpile core-js for correct work.
350-
/regenerator-runtime/, // transpile not needed
368+
/[\\/]regenerator-runtime[\\/]/, // transpile not needed
369+
/[\\/]core-js(|-pure)[\\/]/, // Babel should not transpile core-js for correct work.
351370
],
352371
use: {
353372
loader: 'babel-loader',
@@ -360,8 +379,7 @@ ${ pkg.name } v${ pkg.version } for vue${ vueTarget }
360379

361380
...!noPresetEnv ? [
362381
[
363-
'@babel/preset-env',
364-
{
382+
'@babel/preset-env', {
365383
}
366384
]
367385
] : [],
@@ -370,15 +388,22 @@ ${ pkg.name } v${ pkg.version } for vue${ vueTarget }
370388

371389
...!noPresetEnv ? [
372390
[
373-
"polyfill-corejs3",
374-
{
375-
"method": "usage-pure"
391+
basicIdentifierReplacerPlugin, {
392+
___targetBrowserBabelPlugins
393+
}
394+
],
395+
396+
[
397+
'polyfill-corejs3', {
398+
// Allow global scope pollution with polyfills required by actualTargetsBrowsers.
399+
// This is necessary because the code compiled by vue3-sfc-loader also require these polyfills.
400+
'method': 'entry-global'
376401
}
377402
],
403+
378404
[
379-
'babel-plugin-polyfill-regenerator',
380-
{
381-
"method": "usage-pure"
405+
'polyfill-regenerator', {
406+
'method': 'usage-pure'
382407
}
383408
]
384409
] : [],

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
"buffer": "^6.0.3",
7070
"caniuse-api": "^3.0.0",
7171
"compression-webpack-plugin": "^8.0.1",
72+
"core-js": "^3.15.2",
7273
"core-js-pure": "^3.15.2",
7374
"cross-env": "^7.0.3",
7475
"dts-bundle": "^0.7.3",

src/bootstrap.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// polyfill only stable `core-js` features - ES and web standards:
2+
import "core-js/stable";
3+
14
// 1/
25
//
36
// add sticky flag support

src/createVue2SFCModule.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ export { version as vueVersion } from 'vue-template-compiler/package.json'
4646
*/
4747
const version : string = process.env.VERSION;
4848

49+
// @ts-ignore
50+
const targetBrowserBabelPluginsHash : string = hash(...Object.keys({ ...(typeof ___targetBrowserBabelPlugins !== 'undefined' ? ___targetBrowserBabelPlugins : {}) }));
51+
4952
const genSourcemap : boolean = !!process.env.GEN_SOURCEMAP;
5053

5154
/**
@@ -76,7 +79,7 @@ export async function createSFCModule(source : string, filename : AbstractPath,
7679

7780
const customBlockCallbacks : CustomBlockCallback[] = customBlockHandler !== undefined ? await Promise.all( descriptor.customBlocks.map((block ) => customBlockHandler(block, filename, options)) ) : [];
7881

79-
const componentHash = hash(strFilename, version);
82+
const componentHash = hash(strFilename, version, targetBrowserBabelPluginsHash);
8083
const scopeId = `data-v-${componentHash}`;
8184

8285
// hack: asynchronously preloads the language processor before it is required by the synchronous preprocessCustomRequire() callback, see below

src/createVue3SFCModule.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ type PreprocessLang = SFCAsyncStyleCompileOptions['preprocessLang'];
5050
*/
5151
const version : string = process.env.VERSION;
5252

53+
// @ts-ignore
54+
const targetBrowserBabelPluginsHash : string = hash(...Object.keys({ ...(typeof ___targetBrowserBabelPlugins !== 'undefined' ? ___targetBrowserBabelPlugins : {}) }));
55+
5356
const genSourcemap : boolean = !!process.env.GEN_SOURCEMAP;
5457

5558
/**
@@ -80,7 +83,7 @@ export async function createSFCModule(source : string, filename : AbstractPath,
8083

8184
const customBlockCallbacks : CustomBlockCallback[] = customBlockHandler !== undefined ? await Promise.all( descriptor.customBlocks.map((block) => customBlockHandler(block, filename, options)) ) : [];
8285

83-
const componentHash = hash(strFilename, version);
86+
const componentHash = hash(strFilename, version, targetBrowserBabelPluginsHash);
8487
const scopeId = `data-v-${componentHash}`;
8588

8689
const hasScoped = descriptor.styles.some(e => e.scoped);

src/tools.ts

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,6 @@ import {
2121
// @ts-ignore (Could not find a declaration file for module '@babel/plugin-transform-modules-commonjs')
2222
import babelPluginTransformModulesCommonjs from '@babel/plugin-transform-modules-commonjs'
2323

24-
// @ts-ignore
25-
import pluginProposalOptionalChaining from "@babel/plugin-proposal-optional-chaining"
26-
27-
// @ts-ignore
28-
import pluginProposalNullishCoalescingOperator from '@babel/plugin-proposal-nullish-coalescing-operator'
29-
3024

3125
import * as SparkMD5 from 'spark-md5'
3226

@@ -207,6 +201,10 @@ export function parseDeps(fileAst : t.File) : string[] {
207201
}
208202

209203

204+
// @ts-ignore
205+
const targetBrowserBabelPlugins = { ...(typeof ___targetBrowserBabelPlugins !== 'undefined' ? ___targetBrowserBabelPlugins : {}) };
206+
207+
210208
/**
211209
* @internal
212210
*/
@@ -220,8 +218,8 @@ export async function transformJSCode(source : string, moduleSourceType : boolea
220218
sourceType: moduleSourceType ? 'module' : 'script',
221219
sourceFilename: filename.toString(),
222220
plugins: [
223-
'optionalChaining',
224-
'nullishCoalescingOperator',
221+
// 'optionalChaining',
222+
// 'nullishCoalescingOperator',
225223
...additionalBabelParserPlugins !== undefined ? additionalBabelParserPlugins : [],
226224
],
227225
});
@@ -238,8 +236,8 @@ export async function transformJSCode(source : string, moduleSourceType : boolea
238236
sourceMaps: genSourcemap, // doc: https://babeljs.io/docs/en/options#sourcemaps
239237
plugins: [ // https://babeljs.io/docs/en/options#plugins
240238
...moduleSourceType ? [ babelPluginTransformModulesCommonjs ] : [], // https://babeljs.io/docs/en/babel-plugin-transform-modules-commonjs#options
241-
pluginProposalOptionalChaining,
242-
pluginProposalNullishCoalescingOperator,
239+
// @ts-ignore
240+
...Object.values(targetBrowserBabelPlugins),
243241
...additionalBabelPlugins !== undefined ? Object.values(additionalBabelPlugins) : [],
244242
],
245243
babelrc: false,

0 commit comments

Comments
 (0)