webpack san兼容ie8配置

{
  "name": "front-end",
  "version": "0.0.2",
  "scripts": {
    "dev": "cross-env NODE_ENV=development webpack --mode=development --config webpack.config.js -w",
    "dist": "cross-env NODE_ENV=production webpack --mode=production --progress --config webpack.config.js"
  },
  "devDependencies": {
    "@babel/core": "^7.0.0",
    "@babel/plugin-proposal-class-properties": "^7.0.0",
    "@babel/plugin-proposal-decorators": "^7.0.0",
    "@babel/plugin-proposal-do-expressions": "^7.0.0",
    "@babel/plugin-proposal-export-default-from": "^7.0.0",
    "@babel/plugin-proposal-export-namespace-from": "^7.0.0",
    "@babel/plugin-proposal-function-bind": "^7.0.0",
    "@babel/plugin-proposal-function-sent": "^7.0.0",
    "@babel/plugin-proposal-json-strings": "^7.0.0",
    "@babel/plugin-proposal-logical-assignment-operators": "^7.0.0",
    "@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0",
    "@babel/plugin-proposal-numeric-separator": "^7.0.0",
    "@babel/plugin-proposal-optional-chaining": "^7.0.0",
    "@babel/plugin-proposal-pipeline-operator": "^7.0.0",
    "@babel/plugin-proposal-throw-expressions": "^7.0.0",
    "@babel/plugin-syntax-dynamic-import": "^7.0.0",
    "@babel/plugin-syntax-import-meta": "^7.0.0",
    "@babel/plugin-transform-member-expression-literals": "^7.0.0",
    "@babel/plugin-transform-property-literals": "^7.0.0",
    "@babel/plugin-transform-runtime": "^7.0.0",
    "@babel/preset-env": "^7.0.0",
    "@up/file-upload": "^1.0.6",
    "autoprefixer": "^6.7.7",
    "babel-loader": "^8.0.2",
    "clean-webpack-plugin": "^0.1.16",
    "cross-env": "^5.1.6",
    "css-loader": "^0.28.11",
    "file-loader": "^1.1.11",
    "html-webpack-plugin": "^3.2.0",
    "mini-css-extract-plugin": "^0.4.0",
    "postcss-loader": "^2.1.5",
    "postcss-preset-env": "^5.1.0",
    "precss": "^3.1.2",
    "style-loader": "^0.17.0",
    "uglifyjs-webpack-plugin": "^2.0.1",
    "url-loader": "^0.5.8",
    "webpack": "^4.19.0",
    "webpack-bundle-analyzer": "^3.0.3",
    "webpack-cli": "^3.1.0"
  },
  "dependencies": {
    "@babel/runtime": "^7.0.0",
    "babel-polyfill": "^6.26.0",
    "san": "^3.6.13",
    "san-router": "^1.2.0",
    "san-store": "^1.1.0"
  }
}

index.tpl

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="keywords" content="<%=htmlWebpackPlugin.options.extra.keywords%>">
    <meta name="description" content="<%=htmlWebpackPlugin.options.extra.description%>"/>
    <meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no,minimal-ui">
    <meta name="format-detection" content="telephone=no,email=no"/>
    <title><%=htmlWebpackPlugin.options.title%></title>
    <!--[if lt IE 9]>
    <script src="https://lib.baomitu.com/es5-shim/4.5.12/es5-shim.min.js"></script>
    <script src="https://lib.baomitu.com/es5-shim/4.5.12/es5-sham.js"></script>
    <script src="https://lib.baomitu.com/json3/3.3.2/json3.min.js"></script>
    <script src="https://lib.baomitu.com/console-polyfill/0.3.0/index.js"></script>
    <![endif]-->
    <!--[if IE]>
    <script src="https://lib.baomitu.com/es6-promise/4.1.1/es6-promise.auto.min.js"></script>
    <script>
        window.isIE = true;
    </script>
    <![endif]-->
    <script src="https://lib.baomitu.com/jquery/1.12.4/jquery.min.js"></script>
</head>
<body>
    <%=htmlWebpackPlugin.options.extra.content%>
</body>
</html>

.babelrc.js

const config = {
    presets: [
        [
            '@babel/preset-env',
            {
                targets: {
                    browsers: [
                        'ie >= 6',
                        '> 0.2%'
                    ]
                }
            }
        ]
    ],
    plugins: [
        '@babel/plugin-transform-property-literals',
        '@babel/plugin-transform-member-expression-literals',
        '@babel/plugin-transform-runtime',
        '@babel/plugin-syntax-dynamic-import',
        '@babel/plugin-syntax-import-meta',
        '@babel/plugin-proposal-class-properties',
        '@babel/plugin-proposal-json-strings',
        [
            '@babel/plugin-proposal-decorators',
            {
                legacy: true
            }
        ],
        '@babel/plugin-proposal-function-sent',
        '@babel/plugin-proposal-export-namespace-from',
        '@babel/plugin-proposal-numeric-separator',
        '@babel/plugin-proposal-throw-expressions',
        '@babel/plugin-proposal-export-default-from',
        '@babel/plugin-proposal-logical-assignment-operators',
        '@babel/plugin-proposal-optional-chaining',
        [
            '@babel/plugin-proposal-pipeline-operator',
            {
                proposal: 'minimal'
            }
        ],
        '@babel/plugin-proposal-nullish-coalescing-operator',
        '@babel/plugin-proposal-do-expressions',
        '@babel/plugin-proposal-function-bind'
    ]
};

module.exports = config;

postcss.config.js

const config = {
    plugins: [
        require('precss'),
        require('autoprefixer')({
            browsers: ['last 3 versions', 'Android >= 4.0'],
        }),
    ]
};

// if (process.env.NODE_ENV !== 'development') {
//     config.plugins.push(require('postcss-sprites')({
//         spritePath: './images',
//         filterBy(image) {
//             // Allow only png files
//             if (!/__sprite/.test(image.url)) {
//                 return Promise.reject();
//             }

//             return Promise.resolve();
//         },
//         retina: true
//     }));
// }

module.exports = config;

webpack.config.js

const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
// const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');

const isDev = process.env.NODE_ENV === 'development';

const clientConfig = {
    // devtool: isDev ? '' : 'hidden-source-map',
    devtool: '',
    entry: {
        main: ['./js/main.js'],
    },
    resolve: {
        extensions: ['.js', '.vue', '.json'],
        alias: {
            san: isDev ? 'san/dist/san.dev.js' : 'san/dist/san.min.js'
        }
    },
    output: {
        path: isDev ? path.resolve(__dirname, '../public/report/ie/dev') : path.resolve(__dirname, '../public/report/ie/dist'),
        publicPath: isDev ? '/report/ie/dev/' : '//cdn.upchinaproduct.com/info/StockResearchReportWebServer/report/ie/dist/',
        filename: isDev ? '[name].js' : '[name].[chunkhash:8].js',
        chunkFilename: isDev ? '[name].js' : '[name].[chunkhash:8].js',
    },
    module: {
        rules: [{
            test: /\.js$/,
            exclude: /(node_modules)\\(?!@up)/,
            loader: 'babel-loader?cacheDirectory',
            options: {
                configFile: path.resolve(__dirname, './.babelrc.js')
            }
        }, {
            test: /\.css$/,
            exclude: /node_modules\\(?!@up)/,
            use: [
                MiniCssExtractPlugin.loader,
                isDev ? 'css-loader' : 'css-loader?modules=true&localIdentName=[local]&minimize=true'
            ]
        },
        {
            test: /\.scss$/,
            exclude: /node_modules/,
            use: [
                MiniCssExtractPlugin.loader,
                isDev ? 'css-loader' : 'css-loader?modules=true&localIdentName=[local]&minimize=true',
                'postcss-loader'
            ]
        },
        {
            test: /\.(png|jpg|jpeg|gif)$/,
            use: [{
                loader: 'url-loader',
                options: {
                    limit: 1,
                    name: isDev ? '[name].[ext]' : '[name]-[hash:8].[ext]'
                }
            }]
        }
        ]
    },
    plugins: [
        new webpack.DefinePlugin({
            'process.env': {
                NODE_ENV: `"${process.env.NODE_ENV}"`
            }
        }),
        new HtmlWebpackPlugin({
            title: '研报',
            inject: 'body',
            extra: {
                content: '<div id="app"></div>',
                description: '描述',
                keywords: '关键词'
            },
            filename: path.resolve(__dirname, '../views/indexIE.ejs'),
            chunks: ['manifest', 'vendor', 'main'],
            template: path.resolve(__dirname, './index.tpl')
        }),
        new MiniCssExtractPlugin({
            filename: isDev ? '[name].css' : '[name].[contenthash:8].css',
            // chunkFilename: isDev ? '[id].css' : '[id].[contenthash:8].css',
            allChunks: true
        }),
        // new BundleAnalyzerPlugin({ analyzerPort: 8919 })
    ],
    externals: {},
    optimization: {
        runtimeChunk: {
            name: 'manifest'
        },
        minimizer: [
            // 自定义js优化配置,将会覆盖默认配置
            new UglifyJsPlugin({
                exclude: /\.min\.js$/, // 过滤掉以".min.js"结尾的文件,我们认为这个后缀本身就是已经压缩好的代码,没必要进行二次压缩
                cache: true,
                parallel: true, // 开启并行压缩,充分利用cpu
                sourceMap: false,
                extractComments: false, // 移除注释
                uglifyOptions: {
                    compress: {
                        properties: false,
                        warnings: false
                    },
                    output: {
                        beautify: true,
                        quote_keys: true
                    },
                    mangle: {
                        ie8: true
                    },
                    sourceMap: false
                }
            })
        ],
        minimize: !isDev,
        splitChunks: {
            cacheGroups: {
                default: {
                    minChunks: 2,
                    priority: -20,
                }
            }
        }
    }
};

// 发布特殊逻辑
if (!isDev) {
    clientConfig.plugins.push(new CleanWebpackPlugin(
        ['dev', 'dist'], // 匹配删除的文件
        {
            root: path.resolve(__dirname, '../public/report/ie/'), // 根目录
            verbose: true, // 开启在控制台输出信息
            dry: false // 启用删除文件
        }
    ));
}

module.exports = clientConfig;

onhashchangePolyfill.js

const onhashchangeArr = [];
const hashchange = 'hashchange';
const DOC = document;
const documentMode = DOC.documentMode;
const supportHashChange = (`on${hashchange}` in window) && (documentMode === undefined || documentMode > 7);

if (!supportHashChange) {
    const oldAttachEvent = window.attachEvent;
    window.attachEvent = function attachEvent(type, handler) {
        if (type === 'onhashchange') {
            onhashchangeArr.push(handler);
            // console.log('注册了事件', onhashchangeArr.length);
        }
        oldAttachEvent(type, handler);
    };
}

function onhashchangePolyfill() {
    if (supportHashChange) { return; }

    const location = window.location;
    let oldURL = location.href;
    let oldHash = location.hash;


    // check the location hash on a 100ms interval
    setInterval(() => {
        const newURL = location.href;
        const newHash = location.hash;

        // if the hash has changed and a handler has been bound...
        if (newHash !== oldHash) {
            // console.log('路由发生了变化');
            // execute the handler
            if (typeof window.onhashchange === 'function') {
                window.onhashchange({
                    type: 'hashchange',
                    oldURL,
                    newURL
                });
            }

            for (let i = 0, len = onhashchangeArr.length; i < len; i += 1) {
                const o = onhashchangeArr[i];
                o();
            }

            oldURL = newURL;
            oldHash = newHash;
        }
    }, 100);
}
onhashchangePolyfill();

event.js

function stopEvent(event) {
    const e = event;
    if (window.event) { // 这是IE浏览器
        e.cancelBubble = true;// 阻止冒泡事件
        e.returnValue = false;// 阻止默认事件
    } else if (e && e.stopPropagation) { // 这是其他浏览器
        e.stopPropagation();// 阻止冒泡事件
        e.preventDefault();// 阻止默认事件
    }
}

function addEvent(el, type, fn) {
    if (document.addEventListener) {
        el.addEventListener(type, fn, false);
    } else {
        el.attachEvent(`on${type}`, function eventHandler(e) {
            e.target = e.target || e.srcElement;
            fn.call(this, e);
        });
    }
}

module.exports = {
    stopEvent,
    addEvent
};

storage.js

/* eslint-disable */
/*!
 * 本地化存储(localStorage) 组件
 *
 * 版权所有(C) 2013 马超 (zjcn5205@yeah.net)
 *
 * 这一程序是自由软件,你可以遵照自由软件基金会出版的GNU通用公共许可证条款来修改和重新发布
 * 这一程序。或者用许可证的第二版,或者(根据你的选择)用任何更新的版本。
 * 发布这一程序的目的是希望它有用,但没有任何担保。甚至没有适合特定目的的隐含的担保。更详细
 * 的情况请参阅GNU通用公共许可证。
 * 你应该已经和程序一起收到一份GNU通用公共许可证的副本。如果还没有,写信给:
 * The Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA02139, USA
 */
/*
 *[功能描述]
 * 给不支持本地存储的浏览器创建一个 window.localStorage 对象来提供类似接口
 * 该对象支持以下方法或属性
	setItem : function(key, value)
	getItem : function(key)
	removeItem : function(key)
	clear : function()
	length : int
	key : function(i)
	isVirtualObject : true
 * 二次包装的接口 window.LS 提供以下方法和属性(如果有jQuery则同样会扩展该对象),推荐使用
	set : function(key, vlaue)
	get : function(key)
	remove : function(key)
	clear : function()
	each : function(callback) callback接受两个参数 key 和 value
	obj : function() 返回一个对象描述的localStorage副本
	length : int
 *
 *[已知问题、使用限制]
 * 原生本地存储的key是区分大小写的,模拟对象不区分(因为userData不区分key的大小写)
 * 模拟对象的 clear 方法仅仅能清理通过本组件设定的数据
 * 模拟对象的实际的存储容量跟原生本地存储有差异
 * 模拟对象不支持任何localStorage事件件
 *
 *[更新日志]
 * 2012-06-20 马超 创建
 * 2012-06-27 马超 增加clear相关方法和属性
 * 2012-07-02 马超 修改节点存储方式
 * 2012-07-03 马超 增加二次包装以优化接口使用
 * 2012-07-04 马超 修改内部逻辑,取消原有的单独存储key的方案,修改查询不存在key的时候的默认值为undefined
 * 2012-07-05 马超 增加二次包装的obj方法
 * 2013-03-06 胡志明 基于瑞星的刘瑞明提供的方案,兼容360急速浏览器IE模式(IE6),降低浏览器自带localStorage优先级,优先使用userData。
 * 2013-03-11 胡志明 恢复iframe代理创建head标签用户存储数据,修正了userData无法垮目录读写问题
 * 2013-03-14 胡志明 对部分无法创建iframe代理的浏览器尝试使用自带localStorage,如果不自带则暂时不支持本地存储
 * 2013-03-18 马超 优化加载判断逻辑和代码,以最大限度保证组件可用
 * 2013-04-23 马超 增加更多的错误处理,进一步提高浏览器的兼容性
 * 2013-05-04 马超 增加对userData的key的无效字符的转义处理功能
 * 2013-06-06 马超 优先探测本地存储,解决IE9下userData使用问题(刷新页面后无效)
 */
(function(window){
	//准备模拟对象、空函数等
	var LS, noop = function(){}, document = window.document, notSupport = {set:noop,get:noop,remove:noop,clear:noop,each:noop,obj:noop,length:0};

	//优先探测userData是否支持,如果支持,则直接使用userData,而不使用localStorage
	//以防止IE浏览器关闭localStorage功能或提高安全级别(*_* 万恶的IE)
	//万恶的IE9(9.0.11)),使用userData也会出现类似seesion一样的效果,刷新页面后设置的东西就没有了...
	//只好优先探测本地存储,不能用再尝试使用userData
	(function(){
		// 先探测本地存储 2013-06-06 马超
		// 尝试访问本地存储,如果访问被拒绝,则继续尝试用userData,注: "localStorage" in window 却不会返回权限错误
		// 防止IE10早期版本安全设置有问题导致的脚本访问权限错误
		if( "localStorage" in window ){
			try{
				LS = window.localStorage;
				return;
			}catch(e){
				//如果报错,说明浏览器已经关闭了本地存储或者提高了安全级别
				//则尝试使用userData
			}
		}

		//继续探测userData
		var o = document.getElementsByTagName("head")[0], hostKey = window.location.hostname || "localStorage", d = new Date(), doc, agent;

		//typeof o.addBehavior 在IE6下是object,在IE10下是function,因此这里直接用!判断
		//如果不支持userData则跳出使用原生localStorage,如果原生localStorage报错,则放弃本地存储
		if(!o.addBehavior){
			try{
				LS = window.localStorage;
			}catch(e){
				LS = null;
			}
			return;
		}

		try{ //尝试创建iframe代理,以解决跨目录存储的问题
			agent = new ActiveXObject('htmlfile');
			agent.open();
			agent.write('<s' + 'cript>document.w=window;</s' + 'cript><iframe src="/favicon.ico"></iframe>');
			agent.close();
			doc = agent.w.frames[0].document;
			//这里通过代理document创建head,可以使存储数据垮文件夹访问
			o = doc.createElement('head');
			doc.appendChild(o);
		}catch(e){
			//不处理跨路径问题,直接使用当前页面元素处理
			//不能跨路径存储,也能满足多数的本地存储需求
			//2013-03-15 马超
			o = document.getElementsByTagName("head")[0];
		}

		//初始化userData
		try{
			d.setDate(d.getDate() + 36500);
			o.addBehavior("#default#userData");
			o.expires = d.toUTCString();
			o.load(hostKey);
			o.save(hostKey);
		}catch(e){
			//防止部分外壳浏览器的bug出现导致后续js无法运行
			//如果有错,放弃本地存储
			//2013-04-23 马超 增加
			return;
		}
		//开始处理userData
		//以下代码感谢瑞星的刘瑞明友情支持,做了大量的兼容测试和修改
		//并处理了userData设置的key不能以数字开头的问题
		var root, attrs;
		try{
			root = o.XMLDocument.documentElement;
			attrs = root.attributes;
		}catch(e){
			//防止部分外壳浏览器的bug出现导致后续js无法运行
			//如果有错,放弃本地存储
			//2013-04-23 马超 增加
			return;
		}
		var prefix = "p__hack_", spfix = "m-_-c",
			reg1 = new RegExp("^"+prefix),
			reg2 = new RegExp(spfix,"g"),
			encode = function(key){ return encodeURIComponent(prefix + key).replace(/%/g, spfix); },
			decode = function(key){ return decodeURIComponent(key.replace(reg2, "%")).replace(reg1,""); };
		//创建模拟对象
		LS= {
			length: attrs.length,
			isVirtualObject: true,
			getItem: function(key){
				//IE9中 通过o.getAttribute(name);取不到值,所以才用了下面比较复杂的方法。
				return (attrs.getNamedItem( encode(key) ) || {nodeValue: null}).nodeValue||root.getAttribute(encode(key));
			},
			setItem: function(key, value){
				//IE9中无法通过 o.setAttribute(name, value); 设置#userData值,而用下面的方法却可以。
				try{
					root.setAttribute( encode(key), value);
					o.save(hostKey);
					this.length = attrs.length;
				}catch(e){//这里IE9经常报没权限错误,但是不影响数据存储
				}
			},
			removeItem: function(key){
				//IE9中无法通过 o.removeAttribute(name); 删除#userData值,而用下面的方法却可以。
				try{
					root.removeAttribute( encode(key) );
					o.save(hostKey);
					this.length = attrs.length;
				}catch(e){//这里IE9经常报没权限错误,但是不影响数据存储
				}
			},
			clear: function(){
				while(attrs.length){
					this.removeItem( attrs[0].nodeName );
				}
				this.length = 0;
			},
			key: function(i){
				return attrs[i] ? decode(attrs[i].nodeName) : undefined;
			}
		};
		//提供模拟的"localStorage"接口
		if( !("localStorage" in window) )
			window.localStorage = LS;
	})();

	//二次包装接口
	window.LS = !LS ? notSupport : {
		set : function(key, value){
			//fixed iPhone/iPad 'QUOTA_EXCEEDED_ERR' bug
			if( this.get(key) !== undefined ) {
                this.remove(key);
            }
			LS.setItem(key, value);
			this.length = LS.length;
		},
		//查询不存在的key时,有的浏览器返回null,这里统一返回undefined
		get : function(key){
			var v = LS.getItem(key);
			return v === null ? undefined : v;
		},
		remove : function(key){ LS.removeItem(key);this.length = LS.length; },
		clear : function(){ LS.clear();this.length = 0; },
		//本地存储数据遍历,callback接受两个参数 key 和 value,如果返回false则终止遍历
		each : function(callback){
			var list = this.obj(), fn = callback || function(){}, key;
			for(key in list)
				if( fn.call(this, key, this.get(key)) === false )
					break;
		},
		//返回一个对象描述的localStorage副本
		obj : function(){
			var list={}, i=0, n, key;
			if( LS.isVirtualObject ){
				list = LS.key(-1);
			}else{
				n = LS.length;
				for(; i<n; i++){
					key = LS.key(i);
					list[key] = this.get(key);
				}
			}
			return list;
		},
		length : LS.length
	};
	//如果有jQuery,则同样扩展到jQuery
	if( window.jQuery ) window.jQuery.LS = window.LS;
})(window);

module.exports = LS;

Table of Contents