weex脚手架
此篇文章不要注意排版
经上级领导的要求,我们公司开始步入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版本:
三、开始自己的脚手架
首先weex号称可以一套代码跑三端,那么我们暂且区分两端,原生和H5.
网上巴拉巴拉查询一通,可以使用vue-router写单页面,但是据说在原生APP上切换页面的时候很卡,因为是dom级别的切换,于是,查到建议使用navigator来跳转
然后,然后我们就想办法,自己封装一个router,让咱代码既兼容vue-router,也兼容原生。
以下是我的项目目录:
原生端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的
还需要注意的地方:
由于我们也是刚开始接触weex,希望这这只是一个参考案例,毕竟我们也不是高手。