“> < p style =” max-width: 100%; clear: both; min-height: 1em;




image.png

Story 1: Build

Although the slogan of WEEX is write and run at one time, the build process is actually different. Weex-loader is used for native build, while VUe-loader is used for Web build. In addition, there are many differences, so WebPack requires two sets of configuration.

Best practices

Use WebPack to generate two bundles, one is a Vue-Router based Web SPA and the other is a native multi-portal BundleJS

First suppose we develop a bunch of pages under SRC/Views





image.png

Build web configuration

The entry file on the Web side is render.js

import weexVueRenderer from 'weex-vue-render'
Vue.use(weexVueRenderer)Copy the code

main.js

import App from './App.vue'
import VueRouter from 'vue-router'
import routes from './routes'
Vue.use(VueRouter)
var router = new VueRouter({
  routes
})
/* eslint-disable no-new */
new Vue({
  el: '#root',
  router,
  render: h= > h(App)
})

router.push('/')Copy the code

App.vue

<template>
  <transition name="fade" mode="out-in">
    <router-view class=".container" />
  </transition>
</template>
<script>
export default {
    // ...
}
</script>
<style>
// ...
</style>Copy the code

Webpack. Prod. Conf. Js entrance

const webConfig = merge(getConfig('vue'), {
  entry: {
    app: ['./src/render.js'.'./src/app.js']},output: {
       path: path.resolve(distpath, './web'),
       filename: 'js/[name].[chunkhash].js',
       chunkFilename: 'js/[id].[chunkhash].js'},...module: {
     rules: [{test: /\.vue$/,
             loader: 'vue-loader'}]}})Copy the code

Build native configuration

The packaging process of the native end is actually to export each. Vue file under SRC /views into a separate vue instance, which can be realized by writing a Node script

// build-entry.js
require('shelljs/global')
const path = require('path')
const fs = require('fs-extra')

const srcPath = path.resolve(__dirname, '.. /src/views') // Each.vue page
const entryPath = path.resolve(__dirname, '.. /entry/') // The folder where the entry files are stored
const FILE_TYPE = '.vue'

const getEntryFileContent = path= > {
  return '// import App from'${path}${FILE_TYPE}'
/* eslint-disable no-new */
new Vue({
  el: '#root',
  render: h => h(App)
})

  `
}
// Export method
module.exports = _= > {
  // Delete the original directory
  rm('-rf', entryPath)
  // Write entry files for each file
  fs.readdirSync(srcPath).forEach(file= > {
    const fullpath = path.resolve(srcPath, file)
    const extname = path.extname(fullpath)
    const name = path.basename(file, extname)
    if (fs.statSync(fullpath).isFile() && extname === FILE_TYPE) {
    // Write to the vue render instance
      fs.outputFileSync(path.resolve(entryPath, name + '.js'), getEntryFileContent('.. /src/views/' + name))
    }
  })
  const entry = {}
    // Add multiple entries
  fs.readdirSync(entryPath).forEach(file= > {
    const name = path.basename(file, path.extname(path.resolve(entryPath, file)))
    entry[name] = path.resolve(entryPath, name + '.js')})return entry
}Copy the code

Multiple entries are generated and packaged in webpack.build.conf.js

const buildEntry = require('./build_entry')
// ..
/ / weex configuration
const weexConfig = merge(getConfig('weex'), {
  entry: buildEntry(), // Write multiple entries
  output: {
    path: path.resolve(distPath, './weex'),
    filename: 'js/[name].js' // The WEEX environment does not use hash names
  },
  module: {
        rules: [
            {
                test: /\.vue$/,
                loader: 'weex-loader'}]}})module.exports = [webConfig, weexConfig]Copy the code

The final result





image.png





image.png





image.png

Story 2: Using a preprocessor

In a vUE single file, we can configure the preprocessor in vue-loader as follows

{
    test: /\.vue$/,
    loader: 'vue-loader',
    options: {
        loaders: {
          scss: 'vue-style-loader! css-loader! sass-loader', // <style lang="scss">
          sass: 'vue-style-loader! css-loader! sass-loader? indentedSyntax' // <style lang="sass">}}}Copy the code

Weex in native environment actually processed CSS into JSON loading module, so…

  • usevue-loaderThe configured preprocessor is displayed normally in the Web environmentnativeIs invalid
  • There is no global style in native environment, in JS fileimport 'index.css'It’s not valid




Problem One

After studying the weex-Loader source code, it is found that vue does not need to display the loader configuration. Just specify

<style lang="sass">
    @import './index.scss'
</style>Copy the code

Syntax highlighting, perfect!





image.png

Problem Two

Although there is no concept of global styles, separate import style files are supported

<style lang="sass">
    @import './common.scss'
    @import './variables.scss'
    // ...
</style>Copy the code

Story 3: Style differences

This has been described in some detail in official documentation, but there are a few points worth noting

shorthand

Margin: 0, 0, 10px 10px is not supported

The background color

Note that android views have a default color of white, while iOS does not have a default color if it is not set

Floating point error

Weex uses 750px * 1334px as the default size. In actual rendering, due to floating point error, there may be a few px error, such as fine lines, etc. You can add or subtract several px to debug

Nested writing

Even with a preprocessor, CSS nesting can cause style failures

Story 4: Page jump

There are three types of page redirection in WEEx

  • native -> weex: weexThe page needs a controller as a container, and that’s itnativeThe jump between
  • weex -> native: Required to passmoduleForm jumps by sending events to native
  • Weex -> weex: Use the Navigator module, assuming the two WEEX pages are A.js and B.js, to define mixin methods

      function isWeex () {
        return process.env.COMPILE_ENV === 'weex' // Need to be customized in webpack
      }
    
      export default {
    
        methods: {
    
          push (path) {
            if (isWeex()) {
              const toUrl = weex.config.bundleUrl.split('/').slice(0, -1).join('/') + '/' + path + '.js' // Change the absolute address of a.js to the absolute address of b.js
              weex.requireModule('navigator').push({
                url: toUrl,
                animated: 'true'})}else {
              this.$router.push(path) / / to use vue - the router}},pop () {
            if (isWeex()) {
              weex.requireModule('navigator').pop({
                animated: 'true'})}else {
              window.history.back()
            }
          }
    
        }
    
      }Copy the code

    So the component uses this.push(URL), this.pop() to jump to

Jump configuration

  1. IOS page redirecting does not need to be configured, but Android does. The project generated by using Weexpack Platform Add Android has been configured, but the official documentation does not explain how to access existing applications

    • In Android, intent-filter is used to block jumps

                <activity
                android:name=".WXPageActivity"
                android:label="@string/app_name"
                android:screenOrientation="portrait"
                android:theme="@android:style/Theme.NoTitleBar">
            <intent-filter>
                <action android:name="android.intent.action.VIEW"/>
                <action android:name="com.alibaba.weex.protocol.openurl"/>
      
                <category android:name="android.intent.category.DEFAULT"/>
                <category android:name="com.taobao.android.intent.category.WEEX"/>
      
                <data android:scheme="http"/>
                <data android:scheme="https"/>
                <data android:scheme="file"/>
            </intent-filter>
        </activity>Copy the code
    • Then we create a new WXPageActivity to proxy all weeX page rendering. The core code is as follows

        @Override
        protected void onCreate(Bundle saveInstanceState) {
        // ...
            Uri uri = getIntent().getData();
      Bundle bundle = getIntent().getExtras();
      
        if(uri ! =null) {
          mUri = uri;
        }
      
        if(bundle ! =null) {
          String bundleUrl = bundle.getString("bundleUrl");
          if (!TextUtils.isEmpty(bundleUrl)) {
            mUri = Uri.parse(bundleUrl);
          }
        }
      
        if (mUri == null) {
          Toast.makeText(this."the uri is empty!", Toast.LENGTH_SHORT).show();
          finish();
          return;
        }
        String path = mUri.toString();
        // There is always a bug in the url argument with HTTP :/
        String jsPath = path.indexOf("weex/js/") > 0 ? path.replace("http:/"."") : path;
        HashMap<String, Object> options = new HashMap<String, Object>();
        options.put(WXSDKInstance.BUNDLE_URL, jsPath);
        mWXSDKInstance = new WXSDKInstance(this);
        mWXSDKInstance.registerRenderListener(this);
        mWXSDKInstance.render("weex", WXFileUtils.loadAsset(jsPath, this), options, null, -1, -1, WXRenderStrategy.APPEND_ASYNC);
        }Copy the code

By the way… Weex does not officially provide customizable NAV components which is really inconvenient.. It is often necessary to bridge native through modules to achieve jump requirements

Story 5: Data transfer between pages

  • native -> weex: can be innativeEnd callsrenderWhen the incomingoption, for exampleNSDictary *option = @{@"params": @{}}In theweexThe use ofweex.config.paramsTake out the data
  • weex -> weexUse:storage
  • weex -> nativeUse:The custom module

Story 6: Picture loading

The official website mentions how to load web images, but the behavior of loading local images is definitely inconsistent for the three ends, which means we have to change the path of referencing images to Native again and then package.





But of course there are solutions





image.png

  • Step 1 webpackIt’s easy to set up the image resource to be packaged separatelybundleJsThe image path to access becomes/images/..
   {
     test: /\.(png|jpe? g|gif|svg)$/, loader: 'url-loader', query: { limit:1,
       name: 'images/[hash:8].[name].[ext]'
     }
   }Copy the code
  • Step 2 So now we’re going toJs folder and images folder in the same directoryIn thenativeIn iOSmainBundle, Android generally putsrc/main/assets, the next step is toimgloaderInterface extension to replace the local resource path code is ok

The iOS code is as follows:

- (id<WXImageOperationProtocol>)downloadImageWithURL:(NSString *)url imageFrame:(CGRect)imageFrame userInfo:(NSDictionary *)options completed:(void(^) (UIImage *, NSError *, BOOL))completedBlock{
    if ([url hasPrefix:@ "/ /"]) {
        url = [@"http:" stringByAppendingString:url];
    }
    // Load the local image
    if ([url hasPrefix:@"file://"]) {
        NSString *newUrl = [url stringByReplacingOccurrencesOfString:@"/images/" withString:@ "/"];
        UIImage *image = [UIImage imageNamed:[newUrl substringFromIndex:7]];
        completedBlock(image, nil.YES);
        return (id<WXImageOperationProtocol>) self;
    } else {
        // Load the network image
        return (id<WXImageOperationProtocol>)[[SDWebImageManager sharedManager]downloadImageWithURL:[NSURL URLWithString:url] options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) {
        } completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
            if(completedBlock) { completedBlock(image, error, finished); }}]; }}Copy the code

The Android code is as follows:

  @Override
  public void setImage(final String url, final ImageView view,
                       WXImageQuality quality, final WXImageStrategy strategy) {

    WXSDKManager.getInstance().postOnUiThread(new Runnable() {

      @Override
      public void run(a) {
        if(view==null||view.getLayoutParams()==null) {return;
        }
        if (TextUtils.isEmpty(url)) {
          view.setImageBitmap(null);
          return;
        }
        String temp = url;
        if (url.startsWith("/ /")) {
          temp = "http:" + url;
        }
        if (temp.startsWith("/images/")) {
          // Filter out all relative positions
          temp = temp.replace(".. /"."");
          temp = temp.replace(". /"."");
          // Replace the asset directory configuration
          temp = temp.replace("/images/"."file:///android_asset/weex/images/");
          Log.d("ImageAdapter"."url:" + temp);
        }
        if (view.getLayoutParams().width <= 0 || view.getLayoutParams().height <= 0) {
          return;
        }

        if(! TextUtils.isEmpty(strategy.placeHolder)){ Picasso.Builder builder=new Picasso.Builder(WXEnvironment.getApplication());
          Picasso picasso=builder.build();
          picasso.load(Uri.parse(strategy.placeHolder)).into(view);

          view.setTag(strategy.placeHolder.hashCode(),picasso);
        }

        Picasso.with(WXEnvironment.getApplication())
                .load(temp)
                .into(view, new Callback() {
                  @Override
                  public void onSuccess(a) {
                    if(strategy.getImageListener()! =null){
                      strategy.getImageListener().onImageFinish(url,view,true.null);
                    }

                    if(!TextUtils.isEmpty(strategy.placeHolder)){
                      ((Picasso) view.getTag(strategy.placeHolder.hashCode())).cancelRequest(view);
                    }
                  }

                  @Override
                  public void onError(a) {
                    if(strategy.getImageListener()! =null){
                      strategy.getImageListener().onImageFinish(url,view,false.null); }}}); }},0);
  }Copy the code

Story 7: Practice in the production environment

Incremental updating

Google-diff-match-patch can be used to achieve this. Google-diff-match-patch has many language versions. The idea is as follows:

  • The server builds a management front endbundlejsSystem to provide queriesbundlejsVersion and download API
  • First client accessweexPage to the server to downloadbundlejsfile
  • During each client initialization, the server is silently visited to determine whether it needs to be updated. If it needs to be updated, the server sidediffThe differences between the two versions are returneddiff.nativeEnd usepatch apiGenerate a new version ofbundlejs

down-cycled

Generally, a web interface is deployed at the same time. If a bug occurs on the WEEX page, use the WebView to load the Web version. It is recommended to rely on the server API to control degraded switchover

conclusion

Weex advantage: Rely on VUE, easy to get started. It can meet the requirements of vUE technology-led companies to provide simple/less low-level interaction/hot update pages for Native dual ends

Weex weakness: tweaking at Native is a constant pain in my heart.. As well as the well-known ecological issues, the maintenance team did not spend much effort to answer the community’s questions, and the official document had so many errors that I made a few PR’s while reading it

For the problems not mentioned in the article, welcome to discuss with the author, or refer to my WEEx-start-kit, of course, a star is also very good





image.png