Learning summary, in order to build wheels to measure the learning effect. Recently been doing active page (+++)o (â•Ĩīšâ•Ĩ)o

[Vue Multi-page Project H5] Active page template portalgithub

background

Most of the time, we use WebPack to package single-page applications. In this case, we only need to configure one entry and one template file, but this is not always the case. Sometimes we run into multi-page projects, such as active pages with a short life cycle:

Active page requirements

Mobile TERMINAL H5 often encounters the situation that there is a lot of activity and fast iteration. However, the case of single page is obviously not applicable, mainly reflected in the following aspects:

  1. Repeatedly set up the project, manually 😭 add, low efficiency.
--project1
---node_modules
---src
----style
----common
----components
----views
---package.json

--project2
---node_modules
---src
----style
----common
----components
----views
Copy the code
  1. Our active page H5 has a large number of commonalities:
    • Page adaptation
    • Lots of basic components (loading, Dialog, BTN, poster making, etc.)
    • Most of the themes are consistent
  2. The online deployment of the new active page requires repeated Jenkins configuration, which is inefficient

Based on the above situation, the advantages of multiple pages appear at this time. Let’s talk about the effect of VUe-multi-page after multi-page integration

Vue-multi-page Instruction

Active page H5 multi-page configuration, combined with Jenkins and Nginx implementation, each time to add a page, only need to add a parameter in Jenkins dynamic parameters

Project Structure:

1. Create your own page under Pages

--pages
---demo
----index.html
----app.vue
----main.js

Copy the code

Manual setup? This was not possible, so I wrote a script to add “new:page”: “Node scripts/ createpage.js” to the script.

 const fs = require('fs-extra')
const path = require('path')
const log = require('.. /utils/log')

log.info('[Please enter the page name, for example: pageDemo]')
process.stdin.on('data'.async chunk => {
    const inputName = String(chunk).replace(/\s*/g.' ')
    const pageTarget = path.resolve(__dirname, '.. / '.'src/pages', inputName)
    const pageSource = path.resolve(__dirname, '.. / '.'pageTemplate')
    if(! fs.existsSync(pageTarget)) { log.info(` sourceUrl 】 【${pageSource}`)
        log.info(` targeturl 】 【${pageTarget}`)
        copyPublicFolder(pageSource, pageTarget)
    }else{
        log.err('[Page already exists, please create it again]')
    }
    process.stdin.emit('end')
})

process.stdin.on('end'.() = > {
    process.exit()
})

/** * copy folder *@param {*string} source
 * @param {*string} target 
 */
function copyPublicFolder(source, target) {
    try {
        fs.copySync(source, target)
        log.succes('[page created successfully]')}catch (err) {
        log.error('[page creation failed]')}}Copy the code

To pull from the template, enter the command:

2. Start and package the project

npm install
Copy the code
npm run serve --page=pages/demo
Copy the code
npm run build --page=pages/demo
Copy the code
npm run lint --page=pages/demo
Copy the code

The development of access

npm run serve --page=pages/demo
http://localhost:8888/demo.html#/home
Copy the code

3. The nginx to cooperate

Nginx only needs to configure a root directory for location.

Root points to the packaged dist

	location / {
    	root xxx/dist;
        index index.html;
    }
Copy the code

To add active pages, you only need to add a directory under Pages.

`http://www.bai.cn/page1/#/home`

`http://www.bai.. cn/page2/#/home`
Copy the code

If Jenkins is used for continuous integration, Jenkins’ Choice Parameter can also be used

  1. Choice Parameter Adds an option
page1

page2
Copy the code

  1. Just select the corresponding page when you build.

3. Access address

http://www.bai.cn/page1/#/home

http://www.bai.cn/page2/#/home

4. Built-in environment variables

logo describe
DEV The development environment
TEST The test environment
PRE Pre-release environment
PROD The production environment

5. Vue-cli reference link

See Configuration Reference.

Vue-multi-page Configuration description

1. Configure entry

Vue-cli page attribute allows us to define multiple entry files, with this attribute, to achieve active page multiple page integration.

Each page is a folder in the SRC/directory that contains two subdirectories, one for the page’s template HTML, one for the style file CSS, and one for the entry file index.js

If each page is a directory under./ SRC, the directory name is the page name, and the structure of the directory is the same, there is a common way to get all the page names (e.g. Bargain, demo). An example of this general method is as follows:

const glob = require('glob')
const path = require('path')

/ * * *@param {*String} filterPath 
 * @param {*String} filterStr 
 */
function getEntry (filterPath, filterStr) {
  let globPath = filterPath
  let files = glob.sync(globPath)
  let dirname, entries = {}
  for (let i = 0; i < files.length; i++) {
    dirname = path.dirname(files[i])
    if (dirname.includes(filterStr)) {
      entries['index'] = {
        entry: dirname + '/main.js'.template: dirname + '/index.html'
      }
      break}}console.log('getEntry:', entries)
  return entries
}

/ / input
getEntry('src/pages/**/*.html', getNPMParams().page)

/ / output
{
  index: {
    entry: 'src/pages/demo/main.js'.template: 'src/pages/demo/index.html'}}Copy the code

SRC /pages/**/*.html subdirectories, using the glob library, traversing. SRC /pages/**/*.html subdirectories, through the re match the name of the subdirectory.

Now that we’ve got all the page names, we’re good to go.

Next, we want to package a pages/demo by configuring the NPM command argument:

npm run serve --page=pages/demo

npm run build --page=pages/demo

npm run lint --page=pages/demo
Copy the code

This way, as long as you get the arguments to the NPM command line through process.argv, you know which page you want to run or build.

The specific acquisition method is as follows:

function getNPMParams() {
  let argv
  try {
    argv = JSON.parse(process.env.npm_config_argv).original
  } catch (ex) {
    argv = process.argv
  }
  console.log('argv----', argv)
  const params = {}
  argv &&
  argv.forEach(item= > {
    const arr = item.split(/=/gi)
    if (item.slice(0.2) = = =The '-' && arr.length === 2) {
      params[arr[0].slice(2)] = arr[1]}})if (params && params.page) {
    if(! fs.existsSync(path.resolve(__dirname,'.. /src/', params.page))) {
      console.log(`${params.page}No, please check whether the directory 'exists under Pages)
      process.exit()
    }
  } else {
    console.log('NPM run serve --page=pages/ XXXX')
    process.exit()
  }
  return params
}
Copy the code

Vue.config. js specific configuration

const { getEntry, getNPMParams } = require('./webpack/utils')
const entry = getEntry('src/pages/**/*.html', getNPMParams().page)
const IS_PRODUCTION = process.env.ENV === 'prod'
const port = 8888
const pageName = getNPMParams().page.split('/') [1]

module.exports = {
  publicPath: '/'.lintOnSave: !IS_PRODUCTION,
  // Build according to the entry
  pages: entry,
  // Customize the output
  outputDir: 'dist/' + pageName,
  devServer: {
    port: port,
    disableHostCheck: true
    // compress: true // GZIP}}Copy the code

2. Configure the environment

Actually,vue-cliThe configuration of patterns and environments has been provided

Specifically, because,vue-cliThe provided mode and environment configuration are distributed in different env files according to different environments, which is not convenient for comparison and reference.

So, the integration project uses cross-env and DefinePlugin to configure the environment by injecting environment variables. There will be only one env.js configuration:

const localConfig = {
  DEV: {
    ENV: 'dev'.BASE_API: ' ',},TEST: {
    ENV: 'test'.BASE_API: ' ',},PRE: {
    ENV: 'pre'.BASE_API: ' ',},PROD: {
    ENV: 'prod'.BASE_API: ' ',}}module.exports = (conf= > {
  const systemEnvs = ["DEV"."PROD"."TEST"."PRE"]

  systemEnvs.forEach(env= > {
    conf[env] = Object.assign(
      {
        BASE_API: "/".PROCESS_ENV: env.toLocaleLowerCase(),
        NODE_ENV: "production"
      },
      conf[env] || {}
    )
  })
  return conf
})(localConfig)

Copy the code

Next, inject the environment variables into the chainWebpack of vue.config.js

const path = require('path')

const { getEntry, getNPMParams } = require('./webpack/utils')
const entry = getEntry('src/pages/**/*.html', getNPMParams().page)
const IS_PRODUCTION = process.env.ENV === 'prod'
const ENV_CONFIG = require('./config/env')
const port = 8888
const pageName = getNPMParams().page.split('/') [1]

module.exports = {
  publicPath: '/'.lintOnSave: !IS_PRODUCTION,
  // Build according to the entry
  pages: entry,
  // Customize the output
  outputDir: 'dist/' + pageName,
  devServer: {
    port: port,
  },
  chainWebpack: config= > {
    // Inject environment variables
    config.plugin('define').tap(args= > {
      args[0] ['process.env'] = JSON.stringify(ENV_CONFIG[(process.env.PROCESS_ENV).toLocaleUpperCase()])
      return args
    })
  }
 }
Copy the code

3. The flexible configuration

The flexible solution is adopted for the adaptive solution, and the configurations are as follows:

module.exports = {
  publicPath: '/'.lintOnSave: !IS_PRODUCTION,
  // Build according to the entry
  pages: entry,
  // Customize the output
  outputDir: 'dist/' + pageName,
  devServer: {
    port: port,
    disableHostCheck: true
    // compress: true // GZIP
  },
  css: {
    loaderOptions: {
      css: {},
      postcss: {
        plugins: [
          require('postcss-pxtorem') ({rootValue: 75.// The base of the conversion (the root font of design 750 is 75)
            selectorBlackList: ['.van'].// The selector to ignore and leave as px.
            // The selector to ignore and leave as px. SelectorBlackList: ['.van'], // The selector to ignore and keep as px.
            propList: [The '*'].// The property can be changed from px to REM.
            minPixelValue: 2 // Sets the minimum pixel value to replace.})]}}}}Copy the code

4. Other optimized configurations

  1. Discard the DLL and selecthard-source-webpack-plugin

Vue – CLI and create-react-app show that there are no useful DLLS because Webpack 4’s packaging performance is good enough that DLL maintenance is necessary.

A better alternative to DLLS is the hard-source-webpack-plugin

  chainWebpack: config= > {
  // Enable caching
  config.plugin('hardSource')
    .use(new HardSourceWebpackPlugin())
 }
Copy the code
  1. Gzip configuration,terserClear console
const path = require('path')
const CompressionWebpackPlugin = require('compression-webpack-plugin')

const { getEntry, getNPMParams } = require('./webpack/utils')
const entry = getEntry('src/pages/**/*.html', getNPMParams().page)
const IS_PRODUCTION = process.env.ENV === 'prod'
const ENV_CONFIG = require('./config/env')
const port = 8888
const pageName = getNPMParams().page.split('/') [1]

module.exports = {
  publicPath: '/'.lintOnSave: !IS_PRODUCTION,
  // Build according to the entry
  pages: entry,
  // Customize the output
  outputDir: 'dist/' + pageName,
  devServer: {
    port: port,
    disableHostCheck: true
    // compress: true // GZIP
  },
  chainWebpack: config= > {
    if (IS_PRODUCTION) {
      // To enable gzip, configure nginx
      config.plugin('compressionPlugin')
        .use(new CompressionWebpackPlugin({
          asset: '[path].gz[query]'.algorithm: 'gzip'.test: /\.(js|css|html|svg)$/,
          threshold: 10240.// Compress gzip when greater than 10K
          minRatio: 0.8 // minRatio = Compressed Size/Original Size
        }))
      // Clear production environment clear console output
      config.optimization.minimizer('terser').tap((args) = > {
        args[0].terserOptions.compress.drop_console = true
        return args
      })
    }
  },
  // The production environment closes sourceMap
  // productionSourceMap: ! IS_PRODUCTION,
}

Copy the code

Project tools class

  1. Judgement tools for each endbrowser.js. Mainly throughnavigator.userAgent
/* Check the browser type */
export const browser = {
  versions: (function() {
    var u = navigator.userAgent
    return {
      trident: u.indexOf('Trident') > -1./ / IE kernel
      presto: u.indexOf('Presto') > -1./ / opera kernel
      webKit: u.indexOf('AppleWebKit') > -1.// Apple, Google kernel
      gecko: u.indexOf('Gecko') > -1 && u.indexOf('KHTML') = = = -1.// The Firefox kernel
      mobile:!!!!! u.match(/AppleWebKit.*Mobile.*/), // Whether it is a mobile terminal
      ios:!!!!! u.match(/\(i[^;] +; ( U;) ? CPU.+Mac OS X/), / / ios terminal
      android: u.indexOf('Android') > -1 || u.indexOf('Adr') > -1./ / android terminal
      iPhone: u.indexOf('iPhone') > -1.// Whether the browser is iPhone or QQHD
      iPad: u.indexOf('iPad') > -1 || u.indexOf('Macintosh') > -1./ / whether the device
      webApp: u.indexOf('Safari') = = = -1.// Whether the web should be a program, without a header and a bottom
      weixin: u.indexOf('MicroMessenger') > -1.// Whether to use wechat (2015-01-22 added)
      qq: u.indexOf(' QQ') > -1 / / whether the QQ
    }
  }()),
  language: (navigator.browserLanguage || navigator.language).toLowerCase()
}

Copy the code
  1. Image lazy loading toolv-lazy

See components – Manual lazyLoad and V-lazy for details

// Introduce the default image
import loadingImg from '@/assets/loading.gif'
let timer = null

// Create a listener
const observer = new IntersectionObserver((entries) = > {
  "// entries are a collection of all monitored objects
  entries.forEach(entry= > {
    // Emitted when the monitored element reaches the threshold and no image is loaded.
    if (entry.isIntersecting || entry.intersectionRatio > 0) {
      if(! entry.target.isLoaded) {const lazyImage = entry.target
        // Set the real image address of img to data-src
        lazyImage.src = lazyImage.dataSrc
        observer.unobserve(lazyImage)
      }
    }
  })
})

export default {
  // Insert The element is inserted into the page, and you can get the DOM element's position directly
  inserted(el, binding, vnode) {
    clearTimeout(timer)
    // Display the default image during initialization
    el.src = loadingImg
    // Bind the image address to the DOM
    el.dataSrc = binding.value

    observer.observe(el)
    // Stop listening when the component is uninstalled
    const vm = vnode.context
    timer = setTimeout(() = > {
      vm.$on('hook:beforeDestroy'.() = > {
        observer.disconnect()
      })
    }, 20)},// Image update triggered
  update(el, binding) {
    el.isLoaded = false
    el.dataSrc = binding.value
  }
}

Copy the code

Finally – project github address

[Vue Multi-page Project H5] Active page template portalgithub

Since then: After completing the above modification, adding the active page in the project in the future only needs to add the corresponding directory under Pages; using Jenkins integrated tool, only needs to add configuration parameters, which is absolutely a once and for all approach.

PS: Welcome to exchange and study, please point out any shortcomings.