[Vue multi-page] Я интегрировал страницу активности h5 вот так

Vue.js

Резюме исследования: измерьте эффект обучения тем, сможете ли вы построить колесо. В последнее время я делаю страницы активности (+++) o(╥﹏╥)o

[Vue Multi-Page Project H5] Active Page Templates Portalgithub

задний план

В большинстве случаев мы используем веб-пакет для упаковки одностраничных приложений.На данный момент нам нужно настроить только одну запись и один файл шаблона, но это не тот случай.Иногда мы также сталкиваемся с многостраничными проектами, такими как некоторые проекты с относительно длительным жизненным циклом.Краткая страница активности:

Требования к странице активности

Ситуация, с которой часто сталкивается мобильный h5, заключается в том, что есть много действий и быстрых итераций. Но одностраничная ситуация явно неприменима, что в основном отражается в следующих аспектах:

  1. Многократно собирать проекты и добавлять их вручную 😭, что неэффективно.
--project1
---node_modules
---src
----style
----common
----components
----views
---package.json

--project2
---node_modules
---src
----style
----common
----components
----views
  1. Наша страница активности h5 имеет много общего:
    • адаптация страницы
    • Большое количество базовых компонентов (таких как: загрузка, диалог, кнопка, изготовление постера и т.д.)
    • Большинство тем одинаковы
  2. Онлайн-развертывание новой страницы активности требует повторной настройки jenkins, что неэффективно.

Исходя из вышеуказанного статус-кво, преимущества многостраничности проявляются именно в это время.Поговорим об этом.После многостраничной интеграцииvue-multi-pageЭффект

vue-многостраничная инструкция по использованию

Многостраничная конфигурация активной страницы h5 в сочетании сjenkinsа такжеnginxДля этого каждый раз, когда вы добавляете страницу, вам нужно толькоjenkinsДобавьте параметр к динамическим параметрам

Структура проекта:

1. Создайте свою страницу под страницами

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

Строить вручную? Невозможно, поэтому написал скрипт (по сути он для реализации функции копирования и копирования папок) добавить в скрипт"new:page": "node scripts/createPage.js"

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

log.info('【请输入页面名称,例如: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('【页面已存在,请重新创建】')
    }
    process.stdin.emit('end')
})

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

/**
 * 复制文件夹
 * @param {*string} source
 * @param {*string} target 
 */
function copyPublicFolder(source, target) {
    try {
        fs.copySync(source, target)
        log.succes('【页面创建成功】')
      } catch (err) {
        log.error('【页面创建失败】')
    }
}
 
 

Введите команду для извлечения из шаблона:

2. инициация проекта и упаковка

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

доступ к разработке

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

3. Сотрудничество с nginx

nginx нужно только настроить корневой каталог местоположения.

rootуказать на упакованныйdistТолько что

	location / {
    	root xxx/dist;
        index index.html;
    }

Таким образом, чтобы добавить страницу активности, вам нужно всего лишьpagesВ новом каталоге вы можете напрямую посетить:

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

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

Если используется непрерывная интеграцияjenkins, вы также можете использоватьjenkinsизChoice Parameter

  1. Choice Parameterдобавить параметры
page1

page2

  1. При построении вы можете напрямую выбрать соответствующую страницу.

3. Адрес доступа

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

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

4. Встроенные переменные среды

логотип описывать
DEV среда разработки
TEST тестовая среда
PRE предварительная среда
PROD Производственная среда

5. Справочная ссылка Vue-CLI

See Configuration Reference.

vue-многостраничные инструкции по настройке

1. входная конфигурация входа

vue-cliизpageАтрибут позволяет нам определить несколько файлов входа, и с помощью этого атрибута реализуется интеграция нескольких страниц активной страницы.

Каждая страница представляет собой папку в каталоге src/. В этой папке есть два подкаталога, в которых хранится шаблон html страницы, файл стиля css и файл ввода index.js.

Так как есть правила, то программирование однозначно возможно.Согласно этому правилу, каждая страница представляет собой каталог в ./src, имя каталога - это имя страницы, и структура в этом каталоге одинаковая. , то можно получить все названия страниц (например, trade, demo) с помощью универсального метода. Пример этого универсального метода выглядит следующим образом:

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
}

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

// 输出
{
  index: {
    entry: 'src/pages/demo/main.js',
    template: 'src/pages/demo/index.html'
  }
}

с помощьюglobЭта библиотека, проходящая через каталог .src/, имеет это правилоsrc/pages/**/*.htmlподкаталог, имя этого подкаталога получается путем обычного сопоставления.

Чтобы получить все имя страницы, ниже проще справиться.

Далее мы собираемся упаковатьpages/demoнастраиваетсяnpmРеализация параметра команды:

npm run serve --page=pages/demo

npm run build --page=pages/demo

npm run lint --page=pages/demo

Таким образом, простоprocess.argvпонятноnpmПараметры командной строки, вы можете узнать, какая страница в данный момент должна быть запущена или построена.

Конкретный метод приобретения выглядит следующим образом:

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) === '--' && 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}不存在,请检查pages下是否有该目录`)
      process.exit()
    }
  } else {
    console.log('输入格式请参考:npm run serve --page=pages/xxxx')
    process.exit()
  }
  return params
}

vue.config.jsКонкретная конфигурация

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,
  // 根据入口构建
  pages: entry,
  // 自定义输出
  outputDir: 'dist/' + pageName,
  devServer: {
    port: port,
    disableHostCheck: true
    // compress: true // GZIP
  }
}

2. Конфигурация среды

фактическиvue-cliКонфигурация схемы и окружения предоставлена

Конкретно, потому что,vue-cliПредоставленный режим и конфигурация среды распределены в разных файлах env в соответствии с разными средами, что неудобно для сравнения и ссылки.

Итак, этот интеграционный проект используетcross-envа такжеDefinePlugin, настройте среду, внедрив переменные среды. Будет только один, когда закончитеenv.jsКонфигурация:

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)

Далее, вvue.config.jsизchainWebpackВставить переменные окружения в

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,
  // 根据入口构建
  pages: entry,
  // 自定义输出
  outputDir: 'dist/' + pageName,
  devServer: {
    port: port,
  },
  chainWebpack: config => {
    // 注入环境变量
    config.plugin('define').tap(args => {
      args[0]['process.env'] = JSON.stringify(ENV_CONFIG[(process.env.PROCESS_ENV).toLocaleUpperCase()])
      return args
    })
  }
 }

3. гибкая конфигурация

Адаптивная схема используетflexibleсхема, конфигурация следующая:

module.exports = {
  publicPath: './',
  lintOnSave: !IS_PRODUCTION,
  // 根据入口构建
  pages: entry,
  // 自定义输出
  outputDir: 'dist/' + pageName,
  devServer: {
    port: port,
    disableHostCheck: true
    // compress: true // GZIP
  },
  css: {
    loaderOptions: {
      css: {},
      postcss: {
        plugins: [
          require('postcss-pxtorem')({
            rootValue: 75, // 换算的基数(设计图750的根字体为75)
            selectorBlackList: ['.van'], // 要忽略的选择器并保留为px。
            // 要忽略的选择器并保留为px。 selectorBlackList: ['.van'], // 要忽略的选择器并保留为px。
            propList: ['*'], // 可以从px更改为rem的属性。
            minPixelValue: 2 // 设置要替换的最小像素值。
          })
        ]
      }
    }
  }
}

4. Другая оптимизированная конфигурация

  1. Откажитесь от DLL, выберитеhard-source-webpack-plugin

отvue-cliа такжеcreate-react-appвидно что не практичноdll,Потому что:Webpack 4Производительность упаковки достаточно хороша, и необходимо, чтобы dll продолжала поддерживаться.

Лучшая замена DLL, выбирайтеhard-source-webpack-plugin

  chainWebpack: config => {
  // 启用缓存
  config.plugin('hardSource')
    .use(new HardSourceWebpackPlugin())
 }
  1. gzip-конфигурация,terserочистить консоль
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,
  // 根据入口构建
  pages: entry,
  // 自定义输出
  outputDir: 'dist/' + pageName,
  devServer: {
    port: port,
    disableHostCheck: true
    // compress: true // GZIP
  },
  chainWebpack: config => {
    if (IS_PRODUCTION) {
      // 开启gzip,需要配置nginx
      config.plugin('compressionPlugin')
        .use(new CompressionWebpackPlugin({
          asset: '[path].gz[query]',
          algorithm: 'gzip',
          test: /\.(js|css|html|svg)$/,
          threshold: 10240, // 大于10K才压缩gzip
          minRatio: 0.8 // 压缩比例(minRatio = Compressed Size / Original Size)
        }))
      // 清除生产环境清除控制台输出
      config.optimization.minimizer('terser').tap((args) => {
        args[0].terserOptions.compress.drop_console = true
        return args
      })
    }
  },
  // 生产环境关闭sourceMap
  // productionSourceMap: !IS_PRODUCTION,
}

инструменты проекта

  1. Сквозной инструмент оценкиbrowser.js. главным образом черезnavigator.userAgent
/* 判断浏览器类型 */
export const browser = {
  versions: (function() {
    var u = navigator.userAgent
    return {
      trident: u.indexOf('Trident') > -1, // IE内核
      presto: u.indexOf('Presto') > -1, // opera内核
      webKit: u.indexOf('AppleWebKit') > -1, // 苹果、谷歌内核
      gecko: u.indexOf('Gecko') > -1 && u.indexOf('KHTML') === -1, // 火狐内核
      mobile: !!u.match(/AppleWebKit.*Mobile.*/), // 是否为移动终端
      ios: !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/), // ios终端
      android: u.indexOf('Android') > -1 || u.indexOf('Adr') > -1, // android终端
      iPhone: u.indexOf('iPhone') > -1, // 是否为iPhone或者QQHD浏览器
      iPad: u.indexOf('iPad') > -1 || u.indexOf('Macintosh') > -1, // 是否iPad
      webApp: u.indexOf('Safari') === -1, // 是否web应该程序,没有头部与底部
      weixin: u.indexOf('MicroMessenger') > -1, // 是否微信 (2015-01-22新增)
      qq: u.indexOf(' QQ') > -1 // 是否QQ
    }
  }()),
  language: (navigator.browserLanguage || navigator.language).toLowerCase()
}

  1. Инструмент отложенной загрузки изображенийv-lazy

Для получения подробной информации см.:Компоненты - Ручной разрыв lazyLoad и v-lazy

// 引入默认图片
import loadingImg from '@/assets/loading.gif'
let timer = null

// 创建一个监听器
const observer = new IntersectionObserver((entries) => {
  // entries是所有被监听对象的集合
  entries.forEach(entry => {
    // 当被监听元素到临界值且未加载图片时触发。
    if (entry.isIntersecting || entry.intersectionRatio > 0) {
      if (!entry.target.isLoaded) {
        const lazyImage = entry.target
        // 设置img的真实图片地址data-src
        lazyImage.src = lazyImage.dataSrc
        observer.unobserve(lazyImage)
      }
    }
  })
})

export default {
  // inserted时元素已经插入页面,能够直接获取到dom元素的位置信息
  inserted(el, binding, vnode) {
    clearTimeout(timer)
    // 初始化时展示默认图片
    el.src = loadingImg
    // 将需要加载的图片地址绑定在dom上
    el.dataSrc = binding.value

    observer.observe(el)
    // 在组件卸载的时候停止监听
    const vm = vnode.context
    timer = setTimeout(() => {
      vm.$on('hook:beforeDestroy', () => {
        observer.disconnect()
      })
    }, 20)
  },
  // 图片更新触发
  update(el, binding) {
    el.isLoaded = false
    el.dataSrc = binding.value
  }
}

Наконец - адрес проекта на github

[Многостраничный проект Vue h5] Портал шаблонов активных страницgithub

С тех пор: после завершения вышеописанной модификации, добавления активных страниц в проект в дальнейшем нужно только добавить соответствующую директорию под страницы.Используя инструменты, интегрированные jenkins, вам нужно только добавить параметры конфигурации, что, безусловно, один раз универсальный подход. .

PS: Добро пожаловать на обмен и изучение, пожалуйста, укажите на недостатки.