class RemHot {
constructor() {
this.lib = {};
}
init() {
const { lib } = this;
const win = window;
const doc = win.document;
const docEl = doc.documentElement;
let metaEl = doc.querySelector('meta[name="viewport"]')
const flexibleEl = doc.querySelector('meta[name="flexible"]');
let dpr = 0;
let scale = 0;
let tid;
const flexible = lib.flexible || (lib.flexible = {});
if (metaEl) {
console.warn('将根据已有的meta标签来设置缩放比例');
const match = metaEl.getAttribute('content').match(/initial-scale=([\d.]+)/);
if (match) {
scale = parseFloat(match[1]);
dpr = parseInt(1 / scale, 10);
}
} else if (flexibleEl) {
const content = flexibleEl.getAttribute('content');
if (content) {
const initialDpr = content.match(/initial-dpr=([\d.]+)/);
const maximumDpr = content.match(/maximum-dpr=([\d.]+)/);
if (initialDpr) {
dpr = parseFloat(initialDpr[1]);
scale = parseFloat((1 / dpr).toFixed(2));
}
if (maximumDpr) {
dpr = parseFloat(maximumDpr[1]);
scale = parseFloat((1 / dpr).toFixed(2));
}
}
}
const isIPhone = win.navigator.appVersion.match(/iphone/gi);
if (!dpr && !scale) {
const { devicePixelRatio } = win;
if (isIPhone) {
if (devicePixelRatio >= 3 && (!dpr || dpr >= 3)) {
dpr = 3;
} else if (devicePixelRatio >= 2 && (!dpr || dpr >= 2)) {
dpr = 2;
} else {
dpr = 1;
}
} else {
dpr = 1;
}
scale = 1 / dpr;
}
docEl.setAttribute('data-dpr', dpr);
if (!metaEl) {
metaEl = doc.createElement('meta');
metaEl.setAttribute('name', 'viewport');
metaEl.setAttribute(
'content',
`initial-scale=${scale}, maximum-scale=${scale}, minimum-scale=${scale}, user-scalable=no`,
);
if (docEl.firstElementChild) {
docEl.firstElementChild.appendChild(metaEl);
} else {
const wrap = doc.createElement('div');
wrap.appendChild(metaEl);
doc.write(wrap.innerHTML);
}
}
function refreshRem() {
let { width } = docEl.getBoundingClientRect();
if (width / dpr > 540) {
width = 540 * dpr;
}
const rem = width / 10;
docEl.style.fontSize = `${rem}px`;
doc.body.classList.add(isIPhone ? 'ios' : 'android');
if (win.navigator.appVersion.match(/android/gi)) {
const realfz =
~~(
+window
.getComputedStyle(document.getElementsByTagName('html')[0])
.fontSize.replace('px', '') * 10000
) / 10000;
if (rem !== realfz) {
document.getElementsByTagName('html')[0].style.fontSize = `font-size: ${
rem * (rem / realfz)
}px`;
}
}
flexible.rem = rem;
win.rem = rem;
}
win.addEventListener(
'resize',
() => {
clearTimeout(tid);
tid = setTimeout(refreshRem, 300);
},
false,
);
win.addEventListener(
'pageshow',
e => {
if (e.persisted) {
clearTimeout(tid);
tid = setTimeout(refreshRem, 300);
}
},
false,
);
if (doc.readyState === 'complete') {
doc.body.style.fontSize = `${12 * dpr}px`;
} else {
doc.addEventListener(
'DOMContentLoaded',
() => {
doc.body.style.fontSize = `${12 * dpr}px`;
},
false,
);
}
refreshRem();
flexible.dpr = dpr;
win.dpr = dpr;
flexible.refreshRem = refreshRem;
flexible.rem2px = d => {
let val = parseFloat(d) * this.lib.flexible.rem;
if (typeof d === 'string' && d.match(/rem$/)) {
val += 'px';
}
return val;
};
flexible.px2rem = d => {
let val = parseFloat(d) / this.rem;
if (typeof d === 'string' && d.match(/px$/)) {
val += 'rem';
}
return val;
};
}
}
const remHot = new RemHot();
window['remHot'] = remHot;
export default remHot;
import "core-js/es/promise";
import "core-js/es/array/includes";
import "core-js/es/array/find";
import "core-js/es/array/find-index";
import "core-js/es/array/fill";
import "core-js/es/array/from";
{
"singleQuote": true,
"trailingComma": "all",
"printWidth": 100,
"proseWrap": "never",
"arrowParens": "always",
"overrides": [
{
"files": ".prettierrc",
"options": {
"parser": "json"
}
}
]
}
{
"presets": [
["@babel/preset-env", {
"modules": "commonjs"
}],
"@babel/react"
],
"plugins": [
"syntax-dynamic-import",
"@babel/proposal-class-properties"
]
}
"start": "webpack-dev-server --config webpack.dev.ts",
"build": "npm rebuild node-sass && NODE_ENV=production webpack --config webpack.prod.ts --profile --bail",
"prod": "npm run build && cd dist && static -p 9699",
{
"compilerOptions": {
"baseUrl": "./src/",
"outDir": "./dist/",
"sourceMap": true,
"noImplicitAny": false,
"module": "commonjs",
"target": "es5",
"jsx": "react",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"allowJs": true,
"lib": ["es2015", "dom", "es2016"],
"importHelpers": true,
"experimentalDecorators": true,
"downlevelIteration": true
},
"include": ["./src/**/*"],
"exclude": ["node_modules", "src/**/*.spec.*"]
}
import path from 'path';
import webpack from 'webpack';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
import Settings from './src/config';
const srcPath = './src';
const packageInfo = require('./package.json');
const isProduction = process.env.NODE_ENV === 'production';
const configBase: webpack.Configuration = {
context: __dirname,
target: 'web',
entry: {
react: ['react', 'react-dom'],
vendor: ['react-cookie', 'axios', 'tslib', 'qs', 'zscroller', 'react-portal', 'path-to-regexp', 'moment'],
app: ['./src/utils/PolyfillService.ts', './src/index.tsx'],
},
optimization: {
namedChunks: true,
splitChunks: {
chunks: 'all',
cacheGroups: {
react: {
chunks: 'all',
name: 'react',
test: 'react',
enforce: true,
priority: 10,
},
lib: {
chunks: 'all',
name: 'lib',
test: 'lib',
enforce: true,
priority: 10,
},
vendor: {
chunks: 'all',
name: 'vendor',
test: 'vendor',
enforce: true,
priority: 0,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
},
},
},
module: {
rules: [
{
test: /\.tsx?$/,
use: [
{
loader: 'string-replace-loader',
options: {
search:
'Promise\\.resolve\\(\\)\\.then\\(function \\(\\) \\{[^r]+return [^(]+\\(require\\(([^)]+)\\)\\);[^)]+\\)',
replace: (match, p1) => {
return `import(/* webpackChunkName: "${p1
.split('/')
.pop()
.toLocaleLowerCase()
.replace(/'/g, '')}" */${p1})`;
},
flags: 'g',
},
},
'ts-loader',
],
exclude: /node_modules/,
},
{
test: /\.jsx?$/,
loader: 'babel-loader',
exclude: /node_modules/,
},
{
test: /\.(sa|sc)ss$/,
use: [
isProduction ? MiniCssExtractPlugin.loader : 'style-loader',
'css-loader',
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: [require('autoprefixer')],
},
},
{
loader: 'px2rem-loader',
options: {
baseDpr: 2,
remUnit: 75,
remPrecision: 8,
},
},
{
loader: 'sass-loader',
options: {
sassOptions: {
outputStyle: 'expanded',
},
},
},
{
loader: 'sass-resources-loader',
options: {
resources: [`${srcPath}/styles/_ui_variables.scss`, `${srcPath}/styles/_mixins.scss`],
},
},
],
},
{
test: /\.css$/,
use: [
isProduction ? MiniCssExtractPlugin.loader : 'style-loader',
'css-loader',
{
loader: 'px2rem-loader',
options: {
baseDpr: 1,
remUnit: 37.5,
remPrecision: 8,
},
},
],
},
{
test: /\.(png|jpg|gif)\??.*$/,
loader: 'url-loader',
options: {
limit: '1024',
publicPath: (url) => {
return `/${packageInfo.name}/${url}`;
},
name: 'images/[name].[hash:16].[ext]',
esModule: false,
},
},
{
test: /\.(woff|woff2|eot|ttf|svg)\??.*$/,
loader: 'url-loader',
query: {
limit: '8192',
publicPath: '../',
name: './fonts/[name].[hash:16].[ext]',
useRelativePath: false,
},
},
],
},
resolve: {
modules: [path.join(__dirname, './src'), 'node_modules'],
extensions: ['.tsx', '.ts', '.js', '.jsx'],
},
output: {
path: path.join(__dirname, './dist'),
publicPath: '',
chunkFilename: isProduction
? 'scripts/modules/[id].[contenthash].js'
: 'scripts/modules/[id].[hash].js',
filename: isProduction ? 'scripts/[name].[contenthash:8].js' : 'scripts/[name].[hash:8].js',
},
plugins: [
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
new HtmlWebpackPlugin({
template: './src/index.ejs',
minify: {
minifyJS: true,
collapseWhitespace: true,
removeComments: true,
removeScriptTypeAttributes: true,
},
title: Settings.TRADE_MARK_TITLE,
shortCutIcon: Settings.TRADE_MARK_ICON,
spmHost: Settings.SPM_HOST,
projectName: packageInfo.name,
spmProjectName: packageInfo.name,
}),
],
};
export default configBase;
import path from 'path';
import merge from 'webpack-merge';
import BundleAnalyzer from 'webpack-bundle-analyzer';
import configBase from './webpack.common';
const webpack = require('webpack')
const devPort = 9865;
const webpackDevConfig = merge(configBase, {
mode: 'development',
devServer: {
contentBase: path.join(__dirname, 'dist'),
compress: true,
port: devPort,
historyApiFallback: true,
disableHostCheck: true,
hot: true,
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
],
});
webpackDevConfig.plugins.push(new BundleAnalyzer.BundleAnalyzerPlugin({ analyzerPort: 8891 }));
export default webpackDevConfig;
import TerserPlugin from 'terser-webpack-plugin';
import CopyWebpackPlugin from 'copy-webpack-plugin';
import OptimizeCSSAssetsPlugin from 'optimize-css-assets-webpack-plugin';
import { CleanWebpackPlugin } from 'clean-webpack-plugin';
import merge from 'webpack-merge';
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
import configBase from './webpack.common';
const webpackProdConfig = merge(configBase, {
optimization: {
minimizer: [
new TerserPlugin(),
new OptimizeCSSAssetsPlugin({}),
],
},
performance: {
hints: 'warning',
maxAssetSize: 200000,
maxEntrypointSize: 400000,
},
mode: 'production',
plugins: [
new MiniCssExtractPlugin({
filename: 'styles/[name].[contenthash:8].css',
chunkFilename: 'styles/[id].[contenthash:8].css',
})],
});
webpackProdConfig.plugins.unshift(
new CleanWebpackPlugin({ cleanAfterEveryBuildPatterns: ['dist'] }),
);
webpackProdConfig.plugins.unshift(
new CopyWebpackPlugin({
patterns: [{ from: './src/favicon.ico' }],
}),
);
export default webpackProdConfig;