7

此篇文章不要注意排版

经上级领导的要求,我们公司开始步入weex的队列,虽然现在已经处于开始阶段,但是是一个好的开始,一个艰苦的开始。

废话不多说,我们先聊一聊刚开始的整个过程

一、关于运行weex项目

npm要求5.+,因此安装了node8.7.0,自带安装了 npm 5.4.2
为了方便切换node版本,mac上我们可以安装n来管理
sudo npm n -g
n 8.7.0便已切换

为了 npm install 的速度快一点,设置淘宝镜像
npm config set registry https://registry.npm.taobao.org

二、开始weex

1.安装weex: sudo npm install -g weex-toolkit
2初始化工程:weex init projectName
3.运行demo
weex src/index.vue
然后即可以使用playground app二维码扫描来查看效果了

我的weex版本:

clipboard.png

三、开始自己的脚手架

首先weex号称可以一套代码跑三端,那么我们暂且区分两端,原生和H5.
网上巴拉巴拉查询一通,可以使用vue-router写单页面,但是据说在原生APP上切换页面的时候很卡,因为是dom级别的切换,于是,查到建议使用navigator来跳转

然后,然后我们就想办法,自己封装一个router,让咱代码既兼容vue-router,也兼容原生。
以下是我的项目目录:

clipboard.png

原生端route
weex-routes.js文件

const basePath = 'http://192.168.21.75:8088/dist/views';

const routeList = [
    {path: '/bankList', component: basePath + '/bankList.weex.js'},
    {path: '/bank', component: basePath + '/bank.weex.js'},
    {path: '/home', component: basePath + '/home/home.weex.js'},
    {path: '/material', component: basePath + '/home/material.weex.js'},
    {path: '/user/register', component: basePath + '/user/register/index.weex.js'},
    {path: '/user/modifyPassword', component: basePath + '/user/modifyPassword.index.weex.js'},
];

export default routeList;

web端route配置
web-routes.js文件

import bankList from 'views/bankList.vue';
import bank from 'views/bank.vue';
import home from 'views/home/home.vue';
import material from 'views/home/material.vue';
import register from 'views/user/register/index.vue';
import modifyPassword from 'views/user/modifyPassword/index.vue';

const routeList = [
    {path: '/bankList', component: bankList},
    {path: '/bank', component: bank},
    {path: '/home/home', component: home},
    {path: '/home/material', component: material},
    {path: '/user/register', component: register},
    {path: '/user/modifyPassword', component: modifyPassword},
];

export default routeList;

web端H5由于我们做成一个单页面,所以还需要一个入口文件
app.js文件

import VueRouter from 'vue-router';
import routeList from './web-routes.js';
Vue.use(VueRouter);

const router = new VueRouter({
    routes: routeList,
    mode: 'history'
});

new Vue({
    template: '<div id="root"><router-view></router-view></div>',
    router
}).$mount('#root');

接下来就是我们来封装一下router了,让我们的代码兼容APP和H5端,
router.js文件

import routeList from './weex-routes';
const navigator = weex.requireModule('navigator');

/**
* 从weex路由表中获取路由
* @params route String|Object
*/
function getWeexRoute (route) {
    const item = routeList.find(item => {
        if (item.path === route.path || route === route.path) {
            return item;
        }
    });
    if (!item) {
        throw new Error(`routes路由表中不存在该路径${route.path}`);
    }
    return item;
};


const routerConfig = {
    install () {

        // H5不需要重置router属性,直接返回
        if (weex.config.env.rem) {
            return;
        }
        const url = weex.config.bundleUrl;
        const query = getQueryData(url);

        Object.defineProperty(Vue.prototype, "$router", {
            value: {
                push (route) {
                    const currentRoute = getWeexRoute(route);
                    let query = '';
                    if (route.query) {
                        query = createQuery(route.query);
                    }
                    navigator.push({
                        url: currentRoute.component + query,
                        animated: 'true'
                    });
                },
                back () {
                    if (navigator) {
                        navigator.pop();
                    }
                }
            },
            configurable: false
        });

        Object.defineProperty(Vue.prototype, '$route', {
            configurable: false,
            value: {
                query: query,
                fullPath: '',
                name: '',
                params: {},
                path: '',
                hash: '',
            }
        });
    }
}

Vue.use(routerConfig);

// object 转 URL 参数
function createQuery (obj) {
    let url = '?';
    for (let key in obj) {
        if (obj[key] !== null) {
            url += (key + '=' + encodeURIComponent(obj[key]) + '&');
        }
    }
    return url.substring(0, url.lastIndexOf('&'));
};

// 'xxx.js?name=aa' 转 {name: 'aa'}
function getQueryData (url) {
    url = url.substring(url.indexOf('.js?') + 3);
    var result = {};
    if (url.indexOf("?") != -1) {
        var str = url.substr(1);
        var strs = str.split("&");
        for (var i = 0; i < strs.length; i++) {
            result[strs[i].split("=")[0]] = decodeURIComponent(strs[i].split("=")[1]);
        }
    }
    return result;
 };

ok基础设施已大功告成,我们需要在我们的业务代码中使用router了

// 首先需要引入我们的router.js
import '../../router.js';
this.$router.push({path: '/material', query: this.form});

// 当跳转到material.vue中我们则可以直接获取url中的参数了,此法兼容原生和H5
import '../../router.js';
this.query = this.$route.query;

基础的配置我们已经操作完毕,接下来要配置webpack了
我们需要一个build xx.wexx.js的webpack配置
和一个web的单页的webpack配置

webpack.web.js配置

const ip = require('ip').address();
const path = require('path');
const chalk = require('chalk');
const webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
console.log('server is running! Please open ' + chalk.green('http://' + ip + ':8080/'));
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ScriptExtHtmlWebpackPlugin = require('script-ext-html-webpack-plugin');
const isProd = process.env.NODE_ENV === 'production';


module.exports = function() {
    const config = {
        entry: {
            app: './src/app.js'
        },
        output: {
            path: path.join(__dirname, './dist'),
            filename: '[name].[hash:7].web.js',
        },
        resolve: {
            extensions: ['*', '.vue', '.js'],
            alias: {
                'src': path.join(__dirname, './src'),
                'views': path.join(__dirname, './src/views'),
                'services': path.join(__dirname, './src/services'),
                'utils': path.join(__dirname, './src/utils'),
                'constants': path.join(__dirname, './src/constants'),
                'assets': path.join(__dirname, './src/assets'),
            }
        },
        devtool: 'source-map',
        module: {
            rules: [
                {
                    test: /\.vue(\?[^?]+)?$/,
                    loader: 'vue-loader',
                },
                {
                    test: /\.html$/,
                    loader: 'raw-loader',
                },
                {
                    test: /\.js$/,
                    use: 'babel-loader',
                    exclude: /node_modules/
                }
            ]
        },

        plugins: [
            new webpack.BannerPlugin({
                banner: '// { "framework": ' + ('.vue' === '.vue' ? '"Vue"' : '"Weex"') + '} \n',
                raw: true,
                exclude: 'Vue'
            }),
            new ScriptExtHtmlWebpackPlugin({
                defaultAttribute: 'defer'
            })
        ]
    };

    if (!isProd) {
        config.plugins.push(
            new HtmlWebpackPlugin({
                template: 'web/index.dev.html',
                title: 'Hello Weex',
                isDevServer: true,
                chunksSortMode: 'dependency',
                inject: 'head'
            })
        );

        config.devServer = {
            compress: true,
            host: '0.0.0.0',
            port: '8080',
            historyApiFallback: true,
            public: ip + ':8080',
            watchOptions: {
                aggregateTimeout: 300,
                poll: 1000
            }
        };

    } else {
        // 抽取vue文件css
        config.module.rules[0].options = {
            loaders: {
                css: ExtractTextPlugin.extract({
                    use: ['css-loader'],
                    fallback: 'vue-style-loader'
                })
            }
        };
        config.plugins.push(
            new ExtractTextPlugin('[name].[hash:7].css'),
            new HtmlWebpackPlugin({
                template: 'web/index.html',
                inject: true,
            }),
            new webpack.optimize.UglifyJsPlugin({
                compress: {
                    warnings: false
                }
            })
        )
    }

    return config;

}

原生端的webpack.config.js配置:

const pathTo = require('path');
const fs = require('fs-extra');
const webpack = require('webpack');

const entry = {};
const weexEntry = {};
const vueWebTemp = 'temp';
const hasPluginInstalled = fs.existsSync('./web/plugin.js');
var isWin = /^win/.test(process.platform);


function getEntryFileContent(entryPath, vueFilePath) {
  let relativePath = pathTo.relative(pathTo.join(entryPath, '../'), vueFilePath);
  let contents = '';
  if (hasPluginInstalled) {
    const plugindir = pathTo.resolve('./web/plugin.js');
    contents = 'require(\'' + plugindir + '\') \n';
  }
  if (isWin) {
    relativePath = relativePath.replace(/\\/g,'\\\\');
  }
  contents += 'var App = require(\'' + relativePath + '\')\n';
  contents += 'App.el = \'#root\'\n';
  contents += 'new Vue(App)\n';
  return contents;
}

var fileType = '';

function walk(dir) {
  dir = dir || '.';
  const directory = pathTo.join(__dirname, 'src', dir);
  fs.readdirSync(directory)
    .forEach((file) => {
      const fullpath = pathTo.join(directory, file);
      const stat = fs.statSync(fullpath);
      const extname = pathTo.extname(fullpath);
      if (stat.isFile() && extname === '.vue' || extname === '.we') {
        if (!fileType) {
          fileType = extname;
        }
        if (fileType && extname !== fileType) {
          console.log('Error: This is not a good practice when you use ".we" and ".vue" togither!');
        }
        const name = pathTo.join(dir, pathTo.basename(file, extname));
        if (extname === '.vue') {
          const entryFile = pathTo.join(vueWebTemp, dir, pathTo.basename(file, extname) + '.js');
          fs.outputFileSync(pathTo.join(entryFile), getEntryFileContent(entryFile, fullpath));
          
          entry[name] = pathTo.join(__dirname, entryFile) + '?entry=true';
        } 
        if (fullpath.includes('/views')) {
          weexEntry[name] = fullpath + '?entry=true';
        }
      } else if (stat.isDirectory() && file !== 'build' && file !== 'include') {
        const subdir = pathTo.join(dir, file);
        walk(subdir);
      }
    });
}

walk();
// web need vue-loader
const plugins = [
  new webpack.optimize.UglifyJsPlugin({minimize: true}),
  new webpack.BannerPlugin({
    banner: '// { "framework": ' + (fileType === '.vue' ? '"Vue"' : '"Weex"') + '} \n',
    raw: true,
    exclude: 'Vue'
  })
];

const weexConfig = {
  entry: weexEntry,
  output: {
    path: pathTo.join(__dirname, 'dist'),
    filename: '[name].weex.js',
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        use: [{
          loader: 'babel-loader',
        }],
        exclude: /node_modules(?!\/.*(weex).*)/
      },
      {
        test: /\.vue(\?[^?]+)?$/,
        use: [{
          loader: 'weex-loader'
        }]
      },
      {
        test: /\.we(\?[^?]+)?$/,
        use: [{
          loader: 'weex-loader'
        }]
      }
    ]
  },
  plugins: plugins,
};

module.exports = weexConfig;

package.json配置:

"build": "rm -rf dist && cross-env NODE_ENV=production webpack --config webpack.web.js && webpack --config webpack.config.js",
    "web1": "webpack --config webpack.web.js --watch",
    "web2": "webpack-dev-server --config webpack.web.js --progress --watch --open",
    "web": "rm -rf dist&npm run web1&npm run web2"

打包执行 npm run build,就会把weex和H5的文件都给生产到dist目录中了
.weex文件是原生的,.css .web index.html是H5的

clipboard.png

还需要注意的地方:
由于我们也是刚开始接触weex,希望这这只是一个参考案例,毕竟我们也不是高手。


抹上一抹绿
59 声望3 粉丝

下一篇 »
weex ui组件库