基于vue-cli4的多页面构建配置

vue.config.js

<code class="language-null">const path = require('path');
const glob = require('glob');
const webpack = require('webpack');
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin');
var fs = require('fs');
const CopyPlugin = require('copy-webpack-plugin');
let init_path = process.env.VUE_APP_BUILD_PATH;
let relative_path = init_path;
if (!/^(\.|\/)/.test(init_path)) {
  //补齐相对路径
  relative_path = `./${init_path}`;
}
if (!/\/$/.test(init_path)) {
  //补齐目录斜线
  relative_path = `${relative_path}/`;
}
let entry_path = `${relative_path}**/*.html`;
//配置pages多页面获取当前文件夹下的html和js
function getEntry(globPath) {
  let entries = {},
    basename,
    arr,
    tmp,
    pathname,
    appname;

  glob.sync(globPath).forEach(function(entry) {
    basename = path.basename(entry, path.extname(entry));
    arr = entry.split('/');
    tmp = arr.splice(1, arr.length - 2);
    pathname = basename; // 正确输出js和html的路径
    pagename = basename + '.html';

    entries[pathname] = {
      entry: tmp.join('/') + '/' + basename + '.js',
      template: tmp.join('/') + '/' + pagename,
      title: pagename,
      filename: pagename
    };
  });
  return entries;
}

let pages = getEntry(entry_path);
let event_id = process.env.EVENT_ID;
let is_production = process.env.NODE_ENV == 'production';

//HTML文件输出路径是对的,资源文件路径是错的
let assetsDir = is_production ? `v2/${event_id}/` : `res_dev/`; 
let publicPath = is_production ? '//xxx.com/page/' : '/'

//资源文件路径是对的,HTML文件输出路径是错的

let AddAssetHtmlPluginPath = is_production ? `//xxx.com/page/` : ``;

function getMockDataCb(file){
  return function(req, res){
    let mockData = JSON.parse(fs.readFileSync(`./mock${file}`, 'utf8'));
    res.json(mockData);
  }
};

module.exports = {
  outputDir: `../htdocs/page/`,  //当运行 vue-cli-service build 时生成的生产环境构建文件的目录
  assetsDir: assetsDir, //放置生成的静态资源 (js、css、img、fonts) 的 (相对于 outputDir 的) 目录; serve和build构建都会生效,所以要区分环境
  // indexPath: ``, //指定生成的 index.html 的输出路径 (相对于 outputDir)。也可以是一个绝对路径
  publicPath: publicPath,
  pages,
  devServer: {
    before(app){
      let mockList = glob.sync('./mock/**/*.json').map((item, index) =&gt; {
        return item.replace(/^\.\/mock/, '');
      });
      console.log(mockList)
      mockList.forEach((file, index) =&gt; {
        app.get(file, getMockDataCb(file))
      });
    }
  },
  chainWebpack: config =&gt; {
    config.resolve.alias.set('@', path.resolve(__dirname, './'));
  },
  configureWebpack: config =&gt; {
      let pluginsPro = [
      new webpack.DllReferencePlugin({
        // context: process.cwd(),
        manifest: require('../htdocs/res/vendor/lib-manifest.json')
      })
      ,new webpack.DllReferencePlugin({
        // context: process.cwd(),
        manifest: require('../htdocs/res/vendor/global-manifest.json')
      })
      ,// 将 dll 注入到 生成的 html 模板中
      // ,new HtmlWebpackPlugin()
      new AddAssetHtmlPlugin({
        // dll文件位置
        filepath: path.resolve(__dirname, '../htdocs/res/vendor/*.js'),
        // dll 引用路径
        publicPath: AddAssetHtmlPluginPath,
        // dll最终输出的目录
        outputPath: AddAssetHtmlPluginPath
      })
    ];
    let productPlugins = [
      new CopyPlugin([
        {
          // from: `../htdocs/page/v2/${event_id}/**/*`,
          from: path.resolve(__dirname, `../htdocs/page/v2/${event_id}`)+'/**/*',
          // to: `../htdocs/res/v2/${event_id}/`,
          to: path.resolve(__dirname, `../htdocs/res/v2/${event_id}`),
          force: true,
          toType: 'dir',
          transformPath(targetPath, absolutePath) {
            return targetPath.replace(`htdocs\\page\\v2\\`, '');
          }
        }
      ])
    ];

    if (process.env.NODE_ENV === 'production') {
      // 为生产环境修改配置
      pluginsPro = pluginsPro.concat(productPlugins);
    } else {
      // 为开发环境修改配置
    }
    config.plugins = [...config.plugins, ...pluginsPro];
  }
};
</code>

webpack.dll.conf.js

<code class="language-null">const path = require('path');
const webpack = require('webpack');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
// dll文件存放的目录
const dllPath = '../htdocs/res/vendor';
module.exports = {
  entry: {
    lib: ['vue', 'core-js', 'axios'],
    global: ['zepto', '@/global/flexible.js', '@/global/index.js']
  },
  output: {
    path: path.resolve(__dirname, dllPath),
    filename: '[name].dll.[contenthash].js',
    library: '[name]_dll'
  },
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './')
    }
  },
  module: {
    rules: [{
      test: require.resolve('zepto'),
      loader: 'exports-loader?window.Zepto!script-loader'
    }]
  },
  plugins: [
      //清除之前的dll文件
      new CleanWebpackPlugin(),
      //设置环境变量
      new webpack.DefinePlugin({
          'process.env': {
              NODE_ENV: 'production'
          }
      }),
      //manifest.json描述动态链接库包含了哪些内容
      new webpack.DllPlugin({
          path: path.join(__dirname, dllPath, '[name]-manifest.json'),
          name: '[name]_dll',
          context: process.cwd()
      })
  ]
};
</code>

vue全局ui插件

前言

参照element的方式,写了个简单vue全局ui插件的代码基本组织结构,已经剥离了大部分非关键代码,应该可以一看就懂。

入口js

<code class="language-null">import Vue from 'vue'
import App from './index.vue'
import AuthUi from '@/global/authui.js'

Vue.use(AuthUi);

new Vue({
  render: h =&gt; h(App),
}).$mount('#app')
</code>

插件汇总js @/global/authui.js

<code class="language-null">//参考 https://github.com/ElemeFE/element/blob/dev/src/index.js
import Tips from '@/module/tips/1.0.0/index.js'

const components = [
    Tips
];

const install = function(Vue) {
    components.forEach(component =&gt; {
      Vue.component(component.name, component);
    });

    Vue.prototype.$tips = Tips;

};

/* istanbul ignore if */
if (typeof window !== 'undefined' &amp;&amp; window.Vue) {
    install(window.Vue);
}

export default {
      install,
      Tips
}
</code>

组件入口js @/module/tips/1.0.0/index.js

<code class="language-null">//参考 https://github.com/ElemeFE/element/blob/dev/packages/message/src/main.js
import Vue from 'vue';
import Main from './main.vue';

let TipsConstructor = Vue.extend(Main);

let instance;
let seed = 1;

const Tips = function(options){
    if (typeof options === 'string') {
        options = {
            message: options
        };
    }
    let userOnClose = options.onClose;
    let id = 'tips_' + seed++;
    options.onClose = function(){
        Tips.close(id, userOnClose);
    };
    instance = new TipsConstructor({
        data: options
    });
    instance.id = id;
    instance.$mount();
    document.body.appendChild(instance.$el);
    return instance;
}

export default Tips
</code>

组件模板 @/module/tips/1.0.0/main.vue

<code class="language-null">&lt;template&gt;
&lt;div class="msg-mode"&gt;
    &lt;div class="msg-text text-center"&gt;tips text {{message}}&lt;/div&gt;
&lt;/div&gt;
&lt;/template&gt;
&lt;script&gt;
export default {
    name: 'Tips',
    data(){
        return {
            message: ''
        }
    },
    mounted(){

    }
}
&lt;/script&gt;
&lt;style lang="less" scoped&gt;

&lt;/style&gt;
</code>

关于exports、module.exports和export、export default

使用范围

  • require: node 和 es6 都支持的引入

  • export/import : 只有es6 支持的导出引入

  • module.exports/exports: 只有 node 支持的导出

node 模块

Node里面的模块系统遵循的是CommonJS规范。

CommonJS定义的模块分为: 模块标识(module)、模块定义(exports) 、模块引用(require)

每个模块内部,都有一个module对象,代表当前模块。它有以下属性:

<code class="language-null">module.id 模块的识别符,通常是带有绝对路径的模块文件名。
module.filename 模块的文件名,带有绝对路径。
module.loaded 返回一个布尔值,表示模块是否已经完成加载。
module.parent 返回一个对象,表示调用该模块的模块。
module.children 返回一个数组,表示该模块要用到的其他模块。
module.exports 表示模块对外输出的值。
</code>

为了方便,Node为每个模块提供一个exports变量,指向module.exports。

node中exports和module.exports区别

  • module.exports 初始值为一个空对象 {}
  • exports 是指向的 module.exports 的引用
  • require() 返回的是 module.exports 而不是 exports

因此当直接重新给exports赋值时,外部并不会获取exports的值,而应该使用module.exports

<code class="language-null">//错误写法
exports = {aa: 11}

//正确写法
module.exports = {aa: 11}
</code>

ES中的模块导出导入

export命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系。

<code class="language-null">// 写法一
export var m = 1;

// 写法二
var m = 1;
export {m};

// 写法三
var n = 1;
export {n as m};
</code>

使用export default命令,为模块指定默认输出。

<code class="language-null">// export-default.js
export default function () {
  console.log('foo');
}
</code>
  • export 和 export default的区别:
    1. export与export default均可用于导出常量、函数、文件、模块等
    2. 在一个文件或模块中,export、import可以有多个,export default仅有一个
    3. 通过export方式导出,在导入时要加{ },export default则不需要
    4. export能直接导出变量表达式,export default不行。

参考

exports、module.exports和export、export default到底是咋回事

ECMAScript 6 入门 阮一峰 export命令

金额添加千分符

当前的问题

发现parseFloat(234234).toLocaleString()在给金额添加千分符的时候,safari浏览器会无效,包括ios中的webview中同样无效。

解决方式一:采用纯正则的方式(Safari报错)

<code class="language-null">//采用零宽断言
'12345678.56'.replace(/\d{1,3}(?=(\d{3})+(\.\d+)*$)/g, '$&amp;,').replace(/(?&lt;=\.(\d+\,)*\d+),/g, '')
//输出"12,345,678.56"
</code>

正则主要使用后向引用,采用replace(/\d{1,3}(?=(\d{3})+(\.\d+)*$)/g, '$&,') 匹配前面有三个数字的部分,并添加逗号;使用replace(/(?<=\.(\d+\,)*\d+),/g, '')替换掉小数点后面的逗号。

  • (?=exp) 匹配exp前面的位置
  • (?<=exp) 匹配exp后面的位置
  • (?!exp) 匹配后面跟的不是exp的位置
  • (?<!exp) 匹配前面不是exp的位置
    参考正则表达式后向引用详解

正则中还使用了replace的$&引用,规则如下:

字符 替换文本
$1、$2、...、$99 与 regexp 中的第 1 到第 99 个子表达式相匹配的文本。
$& 与 regexp 相匹配的子串。
$` 位于匹配子串左侧的文本。
$' 位于匹配子串右侧的文本。
$$ 直接量符号。

参考JavaScript replace() 方法

这种方式Chrome下可以正常工作,但是Safari下会报错:Invalid regular expression: unrecognized character after (?,第二个正则报错了。

虽然上面的实现方式看起来挺优雅,然并卵,safari下正则报错,仍旧不能解决问题。

解决方式二:采用半正则半拼接的方式

<code class="language-javascript">//封装的函数
function money2String(amount){
    let amount_arr = amount.toString().split('.');
    amount_arr[0] = amount_arr[0].replace(/\d{1,3}(?=(\d{3})+(\.\d+)*$)/g, '$&amp;,');
    return amount_arr.join('.');
}
</code>

此方法Safari和Chrome均可用,没啥好讲的,over。

vscode自定义代码片段

最近开始用vscode,发现还是挺好用的,微软开发的轻量级开源IDE,支持多平台,插件也很丰富。下面说一个今天get到的技能。设置vscode自定义代码片段,让撸码更快捷。

操作步骤

  • 首先按下快捷键Ctrl+P
  • 输入>snippet,选择“首选项:配置用户代码片段”
  • 输入php,选择php.json
  • 在打开的php.json文件中,编辑模版

编辑模版示例

<code class="language-null">{
    // Place your snippets for php here. Each snippet is defined under a snippet name and has a prefix, body and 
    // description. The prefix is what is used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
    // $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. Placeholders with the 
    // same ids are connected.
    // Example:
    // "Print to console": {
    //  "prefix": "log",
    //  "body": [
    //      "console.log('$1');",
    //      "$2"
    //  ],
    //  "description": "Log output to console"
    // }

    "Print to debug": {
        "prefix": "dd",
        "body": [
            "echo Debug::vars(\\$ddd$0);"
        ],
        "description": "echo log"
    },
    "json request": {
        "prefix": "json",
        "body": [
            "\\$this-&gt;check_view(array('json'));",
            "\\$params = \\$this-&gt;arg_filter(\\$this-&gt;request-&gt;post$0(), array('code'));",
            "\\$v = Validation::factory(\\$params);",
            "\\$v-&gt;rule('code', 'not_empty');",
            "\\$this-&gt;check_valid(\\$v);",
            "\n",
            "\\$ret = \\$this-&gt;itg_interface(array(",
            "\t'parent_code' =&gt; \\$params['code'],",
            "));",
            "\\$this-&gt;view_data = \\$this-&gt;convert_output(\\$ret);"
        ]
    }
}
</code>

注意点

  • 除了php还可以选其他语言
  • prefix表示输入的内容,按tab键后会出现body中的内容
  • $0表示光标位置
  • body中的每一项,表示一行
  • $这种特殊符号要转义,tab要用\t替代,换行要用\n替代,

iPad忘记锁屏密码解决方法

iPad忘记锁屏密码了,密码重试过多,已经不能重新输入密码,提示要连接iTunes,电脑上安装iTunes之后,连接时itunes提示需要iPad解锁屏幕,死锁了- -。

专门跑去苹果店问了下,解决方案如下:

1、电脑安装iTunes,安装后,打开itunes但不用登录苹果帐号

2、iPad连接到电脑

3、同时按住Home键和侧边的锁屏键,长按7、8秒,iPad屏幕变黑时,松开锁屏键,但Home键仍然需要按住,大约5、6秒,电脑上的itunes会出现“iTunes检测到一个处于恢复模式的iPad。您必须先用备份来恢复该iPad然后才能将它与iTunes配合使用。”,点击确定。

4、在电脑上itunes接下来的界面中,选择“恢复iPad”。

5、接下来按提示操作即可,重新激活iPad

 

注意:

1、此方法会让iPad恢复出厂设置,之前的数据会丢失。

2、需要重新激活iPad,可以使用新帐号激活。

Sharp插件

一、关于Sharp
Sharp是用于代理请求的Fiddler插件。
之前代理请求时多使用willow插件,willow的强大之处我就不在此赘述,用过的人都知道。虽然willow很好用,但是仍有痛点,尤其是在代理请求时,像我这种有强迫加懒惰症的人,每次都要选择路径,给文件起名,保存文件到本地,然后设置代理时还要再查找一次文件路径,不够快捷。为了解决文件命名、选择文件保存路径、读取路径的痛点,本插件诞生了~

二、Sharp特性
1、根据设置匹配url路径。
2、直接编辑响应内容,无需在本地保存文件。
3、支持将左侧会话列表拖动到Sharp插件中,并根据会话内容自动填充表单,新建代理配置。
4、匹配POST参数,当请求的url相同而post参数不同时,可通过设置匹配post参数过滤请求。
5、设置别名,可以设置一个方便记忆的名字显示在列表中,方便查找。

三、Sharp插件下载和安装
下载地址:http://realwall.cn/download/sharp_1.1.6.zip
将文件下载的压缩包解压,将Sharp.dll和文件夹Sharp复制到 Fiddler安装目录/Scripts/ 中(注意复制时要关闭Fiddler),启动Fiddler即可在插件栏看到Sharp的选项卡。

四、使用方法
1、打开Fiddler,切换到Sharp选项卡。
2、在浏览器中发起请求,使请求会话出现在Fiddler左侧会话列表中。
3、将需要代理的会话拖动到Sharp的列表中,即出现设置代理窗口。
4、编辑url匹配规则和响应内容,点击确定,则将代理配置添加到了Sharp中了。
5、再次刷新页面,响应即被替换了

 五、注意事项
1、该插件需Fiddler4以上版本使用,低版本插件将不会显示。如果安装后没有出现对应的选项卡,请升级Fiddler版本。

我的折腾list

之前做的东西,有时间没看都快忘了,整理下,方便以后查找。

1、2048朝代版,还可以手机上玩

http://realwall.cn/ts/2048/canvas.html

2、弹性小球动画,一对好基友欢乐的跳来跳去

http://realwall.cn/ts/ball/ball.html

3、弹性小球动画升级版——推球,这个需要在手机上玩,因为只绑了touch事件,没有处理mouse事件。

http://realwall.cn/ts/ball/pushball/ball.html

4、弹性小球再升级——多终端推球小游戏,这个游戏由于需要与服务器端建立websocket连接,需要我启动服务才行。

http://realwall.cn:3000/

5、雪天跑步的人动画,虽然丑的不忍直视,但是毕竟动起来了~

http://realwall.cn/ts/snow_man/index.html

6、变色画板,在手机上打开,选中某一区域可以看到变色效果

http://realwall.cn/ts/draw/index.html

移动端点击事件触发顺序

移动端页面事件触发顺序和事件触发规则不同于PC端,一般都知道click在移动端会延迟200~300ms触发。
本文从实例出发,演示移动端事件的触发规则和顺序。

HTML页面:

<body>
<script type="text/javascript">
var firstEmitTime = 0;
//页面输出日志
function showLog(msg){
    var p = document.createElement('p');
    p.innerHTML = msg;
    document.body.appendChild(p);
}
</script>

<a href="javascript:
    var aa='href: ' + ((new Date()).getTime() - firstEmitTime);
    showLog(aa);console.log(aa);"
    id="aa">This is a href</a>
<script type="text/javascript" src="tmp.js"></script>
</body>

tmp.js文件:

var aa = document.getElementById("aa");
var eventTypeArr = ['click', 'touchstart', 'touchmove', 'touchend', 
        'mousedown', 'mousemove', 'mouseover', 'mouseup'];

for(var i = 0; i < eventTypeArr.length; i++){
    //利用闭包保存eventType,当回调函数触发时会访问该闭包的环境变量对象
    (function(){
        var eventType = eventTypeArr[i];
        aa.addEventListener(eventType, function(){
            var curTime = (new Date()).getTime();
            if(firstEmitTime === 0){
                firstEmitTime = curTime;
            }
            //打印当前事件触发时间与第一个事件触发时间的差值
            var log = eventType + ': ' + (curTime - firstEmitTime);
            console.log(log);
            showLog(log);
        });
    })();
}

iPhone4/iOS7.0.4上如果点击(非长按),则输出结果大多为(事件触发顺序不变,间隔时间略有不同):
touchstart:0
touchend:204
mouseover:243
mousemove:243
果长按链接,经一百多次测试,下面是两种输出结果,约70%是第一种情况(由于受点击时长和力度的影响,此数据仅供参考):
第一种:
touchstart:0
mouseover:2238
mousedown:2239
mouseup:2243
click:2244
href:2245

第二种:
mouseover:0
mousedown:0
mouseup:5
click:5
href:5
其中,href内为JavaScript代码时,浏览器会提示是否打开,需要点击打开,才会输出mouseover及其后面的值,因此与touchstart的间隔可能不是200~300ms。
从结果可以看出该环境下:
1、与click触发时,touchstart未必触发;
2、mouse系列事件与click事件是相近时间内触发,大约在touchstart后延迟200~300ms后触发。

华为MT1-U06/Android4.1.2上点击链接(非长按),测试一百多次,主要有下面三种结果,出现概率分别为70%、25%和5%(由于受点击时长和力度的影响,此数据仅供参考):
第一种:
mouseover:0
mousemove:6
touchstart:12
touchend:568
mousedown:869
mouseup:874
click:876
href:879

第二种:
mouseover:0
mousemove:7
mousedown:387
mouseup:392
click:394
href:396

第三种:
mouseover:0
mousemove:8
touchstart:12
touchmove:91
touchend:95
可以看出该环境下:
1、mouse事件会先于touch事件开始,而迟于其结束。
2、click和href事件触发仍是最后触发,会延迟300ms左右。

页面引入JavaScript脚本

1、直接添加代码块
通过script标签,可直接将JavaScript代码嵌入网页
Example:

<script type="text/javascript">
  document.write("Hello World!")
</script>

2、加载外部脚本

<script charset="utf-8" src="example.js"></script>

直接将script标签写在页面上,浏览器加载时会阻塞页面渲染,知道js加载完成。

异步加载的脚本可以这样引入

<script src="js/require.js" defer async="async"></script>

其中,defer规定是否对脚本执行进行延迟,直到页面加载为止。但只有Internet Explorer支持defer属性。
async属性可以保证脚本下载的同时,浏览器继续渲染。一旦渲染完成,再执行脚本文件,这就是“非同步执行”的意思。需要注意的是,一旦采用这个属性,就无法保证脚本的执行顺序。哪个脚本先下载结束,就先执行那个脚本。IE 10支持async属性,低于这个版本的IE都不支持。如果同时使用async和defer属性,后者不起作用,浏览器行为由async属性决定。

如果 async="async":脚本相对于页面的其余部分异步地执行(当页面继续进行解析时,脚本将被执行)
如果不使用 async 且 defer="defer":脚本将在页面完成解析时执行
如果既不使用 async 也不使用 defer:在浏览器继续解析页面之前,立即读取并执行脚本

3、动态嵌入脚本
创建script元素,插入页面,可动态嵌入脚本,如下:

var jsfile = ['/js/logic/ee.js', '/js/logic/ff.js'];
for(var i = 0; i < jsfile.length; i++){
    var script = document.createElement('script');
    script.src = jsfile[i];
    script.async = false;
    var src = jsfile[i];
    script.onload = function(){
        console.log('onload ' + this.src);
    }
    document.head.appendChild(script);
}

不管async是不是设置为false,都不会阻塞页面渲染。但设置async为false,可保证按照添加到页面的顺序加载js,设置为true或不设置都不会按顺序加载。
还有就是,js加载下来后会立即执行,然后才会执行onload方法。

参考:
http://javascript.ruanyifeng.com/bom/engine.html