yerong 4 år sedan
incheckning
30f48421ce

+ 18 - 0
.babelrc

@@ -0,0 +1,18 @@
+{
+  "presets": [
+    ["env", {
+      "modules": false,
+      "targets": {
+        "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
+      }
+    }],
+    "stage-2"
+  ],
+  "plugins": ["transform-runtime"],
+  "env": {
+    "test": {
+      "presets": ["env", "stage-2"],
+      "plugins": ["istanbul"]
+    }
+  }
+}

+ 9 - 0
.editorconfig

@@ -0,0 +1,9 @@
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 2
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true

+ 13 - 0
.gitignore

@@ -0,0 +1,13 @@
+.DS_Store
+node_modules/
+dist/
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Editor directories and files
+.idea
+*.suo
+*.ntvs*
+*.njsproj
+*.sln

+ 7 - 0
.postcssrc.js

@@ -0,0 +1,7 @@
+// https://github.com/michael-ciniawsky/postcss-load-config
+
+module.exports = {
+  "plugins": {
+    "postcss-mpvue-wxss": {}
+  }
+}

+ 37 - 0
README.md

@@ -0,0 +1,37 @@
+# mpapp
+
+> A Mpvue project
+
+## Build Setup
+
+``` bash
+# 初始化项目
+vue init mpvue/mpvue-quickstart myproject
+cd myproject
+
+# 安装依赖
+yarn
+
+# 开发时构建
+npm dev
+
+# 打包构建
+npm build
+
+# 指定平台的开发时构建(微信、百度、头条、支付宝)
+npm dev:wx
+npm dev:swan
+npm dev:tt
+npm dev:my
+
+# 指定平台的打包构建
+npm build:wx
+npm build:swan
+npm build:tt
+npm build:my
+
+# 生成 bundle 分析报告
+npm run build --report
+```
+
+For detailed explanation on how things work, checkout the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader).

+ 45 - 0
build/build.js

@@ -0,0 +1,45 @@
+require('./check-versions')()
+
+process.env.NODE_ENV = 'production'
+process.env.PLATFORM = process.argv[process.argv.length - 1] || 'wx'
+
+var ora = require('ora')
+var rm = require('rimraf')
+var path = require('path')
+var chalk = require('chalk')
+var webpack = require('webpack')
+var config = require('../config')
+var webpackConfig = require('./webpack.prod.conf')
+var utils = require('./utils')
+
+var spinner = ora('building for production...')
+spinner.start()
+
+rm(path.join(config.build.assetsRoot, '*'), err => {
+  if (err) throw err
+  webpack(webpackConfig, function (err, stats) {
+    spinner.stop()
+    if (err) throw err
+    if (process.env.PLATFORM === 'swan') {
+      utils.writeFrameworkinfo()
+    }
+    process.stdout.write(stats.toString({
+      colors: true,
+      modules: false,
+      children: false,
+      chunks: false,
+      chunkModules: false
+    }) + '\n\n')
+
+    if (stats.hasErrors()) {
+      console.log(chalk.red('  Build failed with errors.\n'))
+      process.exit(1)
+    }
+
+    console.log(chalk.cyan('  Build complete.\n'))
+    console.log(chalk.yellow(
+      '  Tip: built files are meant to be served over an HTTP server.\n' +
+      '  Opening index.html over file:// won\'t work.\n'
+    ))
+  })
+})

+ 48 - 0
build/check-versions.js

@@ -0,0 +1,48 @@
+var chalk = require('chalk')
+var semver = require('semver')
+var packageConfig = require('../package.json')
+var shell = require('shelljs')
+function exec (cmd) {
+  return require('child_process').execSync(cmd).toString().trim()
+}
+
+var versionRequirements = [
+  {
+    name: 'node',
+    currentVersion: semver.clean(process.version),
+    versionRequirement: packageConfig.engines.node
+  }
+]
+
+if (shell.which('npm')) {
+  versionRequirements.push({
+    name: 'npm',
+    currentVersion: exec('npm --version'),
+    versionRequirement: packageConfig.engines.npm
+  })
+}
+
+module.exports = function () {
+  var warnings = []
+  for (var i = 0; i < versionRequirements.length; i++) {
+    var mod = versionRequirements[i]
+    if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
+      warnings.push(mod.name + ': ' +
+        chalk.red(mod.currentVersion) + ' should be ' +
+        chalk.green(mod.versionRequirement)
+      )
+    }
+  }
+
+  if (warnings.length) {
+    console.log('')
+    console.log(chalk.yellow('To use this template, you must update following to modules:'))
+    console.log()
+    for (var i = 0; i < warnings.length; i++) {
+      var warning = warnings[i]
+      console.log('  ' + warning)
+    }
+    console.log()
+    process.exit(1)
+  }
+}

+ 9 - 0
build/dev-client.js

@@ -0,0 +1,9 @@
+/* eslint-disable */
+require('eventsource-polyfill')
+var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true')
+
+hotClient.subscribe(function (event) {
+  if (event.action === 'reload') {
+    window.location.reload()
+  }
+})

+ 111 - 0
build/dev-server.js

@@ -0,0 +1,111 @@
+require('./check-versions')()
+
+process.env.PLATFORM = process.argv[process.argv.length - 1] || 'wx'
+var config = require('../config')
+if (!process.env.NODE_ENV) {
+  process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV)
+}
+
+// var opn = require('opn')
+var path = require('path')
+var express = require('express')
+var webpack = require('webpack')
+var proxyMiddleware = require('http-proxy-middleware')
+var portfinder = require('portfinder')
+var webpackConfig = require('./webpack.dev.conf')
+var utils = require('./utils')
+
+// default port where dev server listens for incoming traffic
+var port = process.env.PORT || config.dev.port
+// automatically open browser, if not set will be false
+var autoOpenBrowser = !!config.dev.autoOpenBrowser
+// Define HTTP proxies to your custom API backend
+// https://github.com/chimurai/http-proxy-middleware
+var proxyTable = config.dev.proxyTable
+
+var app = express()
+var compiler = webpack(webpackConfig)
+if (process.env.PLATFORM === 'swan') {
+  utils.writeFrameworkinfo()
+}
+
+// var devMiddleware = require('webpack-dev-middleware')(compiler, {
+//   publicPath: webpackConfig.output.publicPath,
+//   quiet: true
+// })
+
+// var hotMiddleware = require('webpack-hot-middleware')(compiler, {
+//   log: false,
+//   heartbeat: 2000
+// })
+// force page reload when html-webpack-plugin template changes
+// compiler.plugin('compilation', function (compilation) {
+//   compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
+//     hotMiddleware.publish({ action: 'reload' })
+//     cb()
+//   })
+// })
+
+// proxy api requests
+Object.keys(proxyTable).forEach(function (context) {
+  var options = proxyTable[context]
+  if (typeof options === 'string') {
+    options = { target: options }
+  }
+  app.use(proxyMiddleware(options.filter || context, options))
+})
+
+// handle fallback for HTML5 history API
+app.use(require('connect-history-api-fallback')())
+
+// serve webpack bundle output
+// app.use(devMiddleware)
+
+// enable hot-reload and state-preserving
+// compilation error display
+// app.use(hotMiddleware)
+
+// serve pure static assets
+var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory)
+app.use(staticPath, express.static('./static'))
+
+// var uri = 'http://localhost:' + port
+
+var _resolve
+var readyPromise = new Promise(resolve => {
+  _resolve = resolve
+})
+
+// console.log('> Starting dev server...')
+// devMiddleware.waitUntilValid(() => {
+//   console.log('> Listening at ' + uri + '\n')
+//   // when env is testing, don't need open it
+//   if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') {
+//     opn(uri)
+//   }
+//   _resolve()
+// })
+
+module.exports = new Promise((resolve, reject) => {
+  portfinder.basePort = port
+  portfinder.getPortPromise()
+  .then(newPort => {
+      if (port !== newPort) {
+        console.log(`${port}端口被占用,开启新端口${newPort}`)
+      }
+      var server = app.listen(newPort, 'localhost')
+      // for 小程序的文件保存机制
+      require('webpack-dev-middleware-hard-disk')(compiler, {
+        publicPath: webpackConfig.output.publicPath,
+        quiet: true
+      })
+      resolve({
+        ready: readyPromise,
+        close: () => {
+          server.close()
+        }
+      })
+  }).catch(error => {
+    console.log('没有找到空闲端口,请打开任务管理器杀死进程端口再试', error)
+  })
+})

+ 117 - 0
build/utils.js

@@ -0,0 +1,117 @@
+var path = require('path')
+var fs = require('fs')
+var config = require('../config')
+var ExtractTextPlugin = require('extract-text-webpack-plugin')
+var mpvueInfo = require('../node_modules/mpvue/package.json')
+var packageInfo = require('../package.json')
+var mkdirp = require('mkdirp')
+
+exports.assetsPath = function (_path) {
+  var assetsSubDirectory = process.env.NODE_ENV === 'production'
+    ? config.build.assetsSubDirectory
+    : config.dev.assetsSubDirectory
+  return path.posix.join(assetsSubDirectory, _path)
+}
+
+exports.cssLoaders = function (options) {
+  options = options || {}
+
+  var cssLoader = {
+    loader: 'css-loader',
+    options: {
+      minimize: process.env.NODE_ENV === 'production',
+      sourceMap: options.sourceMap
+    }
+  }
+
+  var postcssLoader = {
+    loader: 'postcss-loader',
+    options: {
+      sourceMap: true
+    }
+  }
+
+  var px2rpxLoader = {
+    loader: 'px2rpx-loader',
+    options: {
+      baseDpr: 1,
+      rpxUnit: 0.5
+    }
+  }
+
+  // generate loader string to be used with extract text plugin
+  function generateLoaders (loader, loaderOptions) {
+    var loaders = [cssLoader, px2rpxLoader, postcssLoader]
+    if (loader) {
+      loaders.push({
+        loader: loader + '-loader',
+        options: Object.assign({}, loaderOptions, {
+          sourceMap: options.sourceMap
+        })
+      })
+    }
+
+    // Extract CSS when that option is specified
+    // (which is the case during production build)
+    if (options.extract) {
+      return ExtractTextPlugin.extract({
+        use: loaders,
+        fallback: 'vue-style-loader'
+      })
+    } else {
+      return ['vue-style-loader'].concat(loaders)
+    }
+  }
+
+  // https://vue-loader.vuejs.org/en/configurations/extract-css.html
+  return {
+    css: generateLoaders(),
+    wxss: generateLoaders(),
+    postcss: generateLoaders(),
+    less: generateLoaders('less'),
+    sass: generateLoaders('sass', { indentedSyntax: true }),
+    scss: generateLoaders('sass'),
+    stylus: generateLoaders('stylus'),
+    styl: generateLoaders('stylus')
+  }
+}
+
+// Generate loaders for standalone style files (outside of .vue)
+exports.styleLoaders = function (options) {
+  var output = []
+  var loaders = exports.cssLoaders(options)
+  for (var extension in loaders) {
+    var loader = loaders[extension]
+    output.push({
+      test: new RegExp('\\.' + extension + '$'),
+      use: loader
+    })
+  }
+  return output
+}
+
+const writeFile = async (filePath, content) => {
+  let dir = path.dirname(filePath)
+  let exist = fs.existsSync(dir)
+  if (!exist) {
+    await mkdirp(dir)
+  }
+  await fs.writeFileSync(filePath, content, 'utf8')
+}
+
+exports.writeFrameworkinfo = function () {
+  var buildInfo = {
+    'toolName': mpvueInfo.name,
+    'toolFrameWorkVersion': mpvueInfo.version,
+    'toolCliVersion': packageInfo.mpvueTemplateProjectVersion || '',
+    'createTime': Date.now()
+  }
+
+  var content = JSON.stringify(buildInfo)
+  var fileName = '.frameworkinfo'
+  var rootDir = path.resolve(__dirname, `../${fileName}`)
+  var distDir = path.resolve(config.build.assetsRoot, `./${fileName}`)
+
+  writeFile(rootDir, content)
+  writeFile(distDir, content)
+}

+ 21 - 0
build/vue-loader.conf.js

@@ -0,0 +1,21 @@
+var utils = require('./utils')
+var config = require('../config')
+// var isProduction = process.env.NODE_ENV === 'production'
+// for mp
+var isProduction = true
+
+module.exports = {
+  loaders: utils.cssLoaders({
+    sourceMap: isProduction
+      ? config.build.productionSourceMap
+      : config.dev.cssSourceMap,
+    extract: isProduction
+  }),
+  transformToRequire: {
+    video: 'src',
+    source: 'src',
+    img: 'src',
+    image: 'xlink:href'
+  },
+  fileExt: config.build.fileExt
+}

+ 142 - 0
build/webpack.base.conf.js

@@ -0,0 +1,142 @@
+var path = require('path')
+var fs = require('fs')
+var utils = require('./utils')
+var config = require('../config')
+var webpack = require('webpack')
+var merge = require('webpack-merge')
+var vueLoaderConfig = require('./vue-loader.conf')
+var MpvuePlugin = require('webpack-mpvue-asset-plugin')
+var glob = require('glob')
+var CopyWebpackPlugin = require('copy-webpack-plugin')
+var relative = require('relative')
+
+function resolve (dir) {
+  return path.join(__dirname, '..', dir)
+}
+
+function getEntry (rootSrc) {
+  var map = {};
+  glob.sync(rootSrc + '/pages/**/main.js')
+  .forEach(file => {
+    var key = relative(rootSrc, file).replace('.js', '');
+    map[key] = file;
+  })
+   return map;
+}
+
+const appEntry = { app: resolve('./src/main.js') }
+const pagesEntry = getEntry(resolve('./src'), 'pages/**/main.js')
+const entry = Object.assign({}, appEntry, pagesEntry)
+
+let baseWebpackConfig = {
+  // 如果要自定义生成的 dist 目录里面的文件路径,
+  // 可以将 entry 写成 {'toPath': 'fromPath'} 的形式,
+  // toPath 为相对于 dist 的路径, 例:index/demo,则生成的文件地址为 dist/index/demo.js
+  entry,
+  target: require('mpvue-webpack-target'),
+  output: {
+    path: config.build.assetsRoot,
+    jsonpFunction: 'webpackJsonpMpvue',
+    filename: '[name].js',
+    publicPath: process.env.NODE_ENV === 'production'
+      ? config.build.assetsPublicPath
+      : config.dev.assetsPublicPath
+  },
+  resolve: {
+    extensions: ['.js', '.vue', '.json'],
+    alias: {
+      'vue': 'mpvue',
+      '@': resolve('src')
+    },
+    symlinks: false,
+    aliasFields: ['mpvue', 'weapp', 'browser'],
+    mainFields: ['browser', 'module', 'main']
+  },
+  module: {
+    rules: [
+      {
+        test: /\.vue$/,
+        loader: 'mpvue-loader',
+        options: vueLoaderConfig
+      },
+      {
+        test: /\.js$/,
+        include: [resolve('src'), resolve('test')],
+        use: [
+          'babel-loader',
+          {
+            loader: 'mpvue-loader',
+            options: Object.assign({checkMPEntry: true}, vueLoaderConfig)
+          },
+        ]
+      },
+      {
+        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
+        loader: 'url-loader',
+        options: {
+          limit: 10000,
+          name: utils.assetsPath('img/[name].[ext]')
+        }
+      },
+      {
+        test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
+        loader: 'url-loader',
+        options: {
+          limit: 10000,
+          name: utils.assetsPath('media/[name].[ext]')
+        }
+      },
+      {
+        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
+        loader: 'url-loader',
+        options: {
+          limit: 10000,
+          name: utils.assetsPath('fonts/[name].[ext]')
+        }
+      }
+    ]
+  },
+  plugins: [
+    // api 统一桥协议方案
+    new webpack.DefinePlugin({
+      'mpvue': 'global.mpvue',
+      'mpvuePlatform': 'global.mpvuePlatform'
+    }),
+    new MpvuePlugin(),
+    new CopyWebpackPlugin([{
+      from: '**/*.json',
+      to: ''
+    }], {
+      context: 'src/'
+    }),
+    new CopyWebpackPlugin([
+      {
+        from: path.resolve(__dirname, '../static'),
+        to: path.resolve(config.build.assetsRoot, './static'),
+        ignore: ['.*']
+      }
+    ])
+  ]
+}
+
+// 针对百度小程序,由于不支持通过 miniprogramRoot 进行自定义构建完的文件的根路径
+// 所以需要将项目根路径下面的 project.swan.json 拷贝到构建目录
+// 然后百度开发者工具将 dist/swan 作为项目根目录打
+const projectConfigMap = {
+  tt: '../project.config.json',
+  swan: '../project.swan.json'
+}
+
+const PLATFORM = process.env.PLATFORM
+if (/^(swan)|(tt)$/.test(PLATFORM)) {
+  baseWebpackConfig = merge(baseWebpackConfig, {
+    plugins: [
+      new CopyWebpackPlugin([{
+        from: path.resolve(__dirname, projectConfigMap[PLATFORM]),
+        to: path.resolve(config.build.assetsRoot)
+      }])
+    ]
+  })
+}
+
+module.exports = baseWebpackConfig

+ 85 - 0
build/webpack.dev.conf.js

@@ -0,0 +1,85 @@
+var utils = require('./utils')
+var webpack = require('webpack')
+var config = require('../config')
+var merge = require('webpack-merge')
+var baseWebpackConfig = require('./webpack.base.conf')
+// var HtmlWebpackPlugin = require('html-webpack-plugin')
+var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
+var MpvueVendorPlugin = require('webpack-mpvue-vendor-plugin')
+
+// copy from ./webpack.prod.conf.js
+var path = require('path')
+var ExtractTextPlugin = require('extract-text-webpack-plugin')
+var CopyWebpackPlugin = require('copy-webpack-plugin')
+var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
+
+// add hot-reload related code to entry chunks
+// Object.keys(baseWebpackConfig.entry).forEach(function (name) {
+//   baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name])
+// })
+
+module.exports = merge(baseWebpackConfig, {
+  module: {
+    rules: utils.styleLoaders({
+      sourceMap: config.dev.cssSourceMap,
+      extract: true
+    })
+  },
+  // cheap-module-eval-source-map is faster for development
+  // devtool: '#cheap-module-eval-source-map',
+  // devtool: '#source-map',
+  output: {
+    path: config.build.assetsRoot,
+    // filename: utils.assetsPath('[name].[chunkhash].js'),
+    // chunkFilename: utils.assetsPath('[id].[chunkhash].js')
+    filename: utils.assetsPath('[name].js'),
+    chunkFilename: utils.assetsPath('[id].js')
+  },
+  plugins: [
+    new webpack.DefinePlugin({
+      'process.env': config.dev.env
+    }),
+
+    // copy from ./webpack.prod.conf.js
+    // extract css into its own file
+    new ExtractTextPlugin({
+      // filename: utils.assetsPath('[name].[contenthash].css')
+      filename: utils.assetsPath(`[name].${config.dev.fileExt.style}`)
+    }),
+    // Compress extracted CSS. We are using this plugin so that possible
+    // duplicated CSS from different components can be deduped.
+    new OptimizeCSSPlugin({
+      cssProcessorOptions: {
+        safe: true
+      }
+    }),
+    new webpack.optimize.CommonsChunkPlugin({
+      name: 'common/vendor',
+      minChunks: function (module, count) {
+        // any required modules inside node_modules are extracted to vendor
+        return (
+          module.resource &&
+          /\.js$/.test(module.resource) &&
+          module.resource.indexOf('node_modules') >= 0
+        ) || count > 1
+      }
+    }),
+    new webpack.optimize.CommonsChunkPlugin({
+      name: 'common/manifest',
+      chunks: ['common/vendor']
+    }),
+    new MpvueVendorPlugin({
+      platform: process.env.PLATFORM
+    }),
+    // https://github.com/glenjamin/webpack-hot-middleware#installation--usage
+    // new webpack.HotModuleReplacementPlugin(),
+    new webpack.NoEmitOnErrorsPlugin(),
+    // https://github.com/ampedandwired/html-webpack-plugin
+    // new HtmlWebpackPlugin({
+    //   filename: 'index.html',
+    //   template: 'index.html',
+    //   inject: true
+    // }),
+    new FriendlyErrorsPlugin()
+  ]
+})

+ 120 - 0
build/webpack.prod.conf.js

@@ -0,0 +1,120 @@
+var path = require('path')
+var utils = require('./utils')
+var webpack = require('webpack')
+var config = require('../config')
+var merge = require('webpack-merge')
+var baseWebpackConfig = require('./webpack.base.conf')
+var UglifyJsPlugin = require('uglifyjs-webpack-plugin')
+var CopyWebpackPlugin = require('copy-webpack-plugin')
+// var HtmlWebpackPlugin = require('html-webpack-plugin')
+var ExtractTextPlugin = require('extract-text-webpack-plugin')
+var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
+var MpvueVendorPlugin = require('webpack-mpvue-vendor-plugin')
+var env = config.build.env
+
+var webpackConfig = merge(baseWebpackConfig, {
+  module: {
+    rules: utils.styleLoaders({
+      sourceMap: config.build.productionSourceMap,
+      extract: true
+    })
+  },
+  devtool: config.build.productionSourceMap ? '#source-map' : false,
+  output: {
+    path: config.build.assetsRoot,
+    // filename: utils.assetsPath('[name].[chunkhash].js'),
+    // chunkFilename: utils.assetsPath('[id].[chunkhash].js')
+    filename: utils.assetsPath('[name].js'),
+    chunkFilename: utils.assetsPath('[id].js')
+  },
+  plugins: [
+    // http://vuejs.github.io/vue-loader/en/workflow/production.html
+    new webpack.DefinePlugin({
+      'process.env': env
+    }),
+    // extract css into its own file
+    new ExtractTextPlugin({
+      // filename: utils.assetsPath('[name].[contenthash].css')
+      filename: utils.assetsPath(`[name].${config.build.fileExt.style}`)
+    }),
+    // Compress extracted CSS. We are using this plugin so that possible
+    // duplicated CSS from different components can be deduped.
+    new OptimizeCSSPlugin({
+      cssProcessorOptions: {
+        safe: true
+      }
+    }),
+    // generate dist index.html with correct asset hash for caching.
+    // you can customize output by editing /index.html
+    // see https://github.com/ampedandwired/html-webpack-plugin
+    // new HtmlWebpackPlugin({
+    //   filename: config.build.index,
+    //   template: 'index.html',
+    //   inject: true,
+    //   minify: {
+    //     removeComments: true,
+    //     collapseWhitespace: true,
+    //     removeAttributeQuotes: true
+    //     // more options:
+    //     // https://github.com/kangax/html-minifier#options-quick-reference
+    //   },
+    //   // necessary to consistently work with multiple chunks via CommonsChunkPlugin
+    //   chunksSortMode: 'dependency'
+    // }),
+    // keep module.id stable when vender modules does not change
+    new webpack.HashedModuleIdsPlugin(),
+    // split vendor js into its own file
+    new webpack.optimize.CommonsChunkPlugin({
+      name: 'common/vendor',
+      minChunks: function (module, count) {
+        // any required modules inside node_modules are extracted to vendor
+        return (
+          module.resource &&
+          /\.js$/.test(module.resource) &&
+          module.resource.indexOf('node_modules') >= 0
+        ) || count > 1
+      }
+    }),
+    // extract webpack runtime and module manifest to its own file in order to
+    // prevent vendor hash from being updated whenever app bundle is updated
+    new webpack.optimize.CommonsChunkPlugin({
+      name: 'common/manifest',
+      chunks: ['common/vendor']
+    }),
+    new MpvueVendorPlugin({
+      platform: process.env.PLATFORM
+    })
+  ]
+})
+
+// if (config.build.productionGzip) {
+//   var CompressionWebpackPlugin = require('compression-webpack-plugin')
+
+//   webpackConfig.plugins.push(
+//     new CompressionWebpackPlugin({
+//       asset: '[path].gz[query]',
+//       algorithm: 'gzip',
+//       test: new RegExp(
+//         '\\.(' +
+//         config.build.productionGzipExtensions.join('|') +
+//         ')$'
+//       ),
+//       threshold: 10240,
+//       minRatio: 0.8
+//     })
+//   )
+// }
+
+if (config.build.bundleAnalyzerReport) {
+  var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
+  webpackConfig.plugins.push(new BundleAnalyzerPlugin())
+}
+
+var useUglifyJs = process.env.PLATFORM !== 'swan'
+if (useUglifyJs) {
+  webpackConfig.plugins.push(new UglifyJsPlugin({
+    sourceMap: true
+  }))
+}
+
+module.exports = webpackConfig

+ 6 - 0
config/dev.env.js

@@ -0,0 +1,6 @@
+var merge = require('webpack-merge')
+var prodEnv = require('./prod.env')
+
+module.exports = merge(prodEnv, {
+  NODE_ENV: '"development"'
+})

+ 68 - 0
config/index.js

@@ -0,0 +1,68 @@
+// see http://vuejs-templates.github.io/webpack for documentation.
+var path = require('path')
+var fileExtConfig = {
+    swan: {
+        template: 'swan',
+        script: 'js',
+        style: 'css',
+        platform: 'swan'
+    },
+    tt: {
+        template: 'ttml',
+        script: 'js',
+        style: 'ttss',
+        platform: 'tt'
+    },
+    wx: {
+        template: 'wxml',
+        script: 'js',
+        style: 'wxss',
+        platform: 'wx'
+    },
+    my: {
+        template: 'axml',
+        script: 'js',
+        style: 'acss',
+        platform: 'my'
+    }
+}
+var fileExt = fileExtConfig[process.env.PLATFORM]
+
+module.exports = {
+  build: {
+    env: require('./prod.env'),
+    index: path.resolve(__dirname, `../dist/${fileExt.platform}/index.html`),
+    assetsRoot: path.resolve(__dirname, `../dist/${fileExt.platform}`),
+    assetsSubDirectory: '',
+    assetsPublicPath: '/',
+    productionSourceMap: false,
+    // Gzip off by default as many popular static hosts such as
+    // Surge or Netlify already gzip all static assets for you.
+    // Before setting to `true`, make sure to:
+    // npm install --save-dev compression-webpack-plugin
+    productionGzip: false,
+    productionGzipExtensions: ['js', 'css'],
+    // Run the build command with an extra argument to
+    // View the bundle analyzer report after build finishes:
+    // `npm run build --report`
+    // Set to `true` or `false` to always turn it on or off
+    bundleAnalyzerReport: process.env.npm_config_report,
+    fileExt: fileExt
+  },
+  dev: {
+    env: require('./dev.env'),
+    port: 8080,
+    // 在小程序开发者工具中不需要自动打开浏览器
+    autoOpenBrowser: false,
+    assetsSubDirectory: '',
+    assetsPublicPath: '/',
+    proxyTable: {},
+    // CSS Sourcemaps off by default because relative paths are "buggy"
+    // with this option, according to the CSS-Loader README
+    // (https://github.com/webpack/css-loader#sourcemaps)
+    // In our experience, they generally work as expected,
+    // just be aware of this issue when enabling this option.
+    cssSourceMap: false,
+    fileExt: fileExt
+  }
+}

+ 3 - 0
config/prod.env.js

@@ -0,0 +1,3 @@
+module.exports = {
+  NODE_ENV: '"production"'
+}

+ 11 - 0
index.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>mpapp</title>
+  </head>
+  <body>
+    <div id="app"></div>
+    <!-- built files will be auto injected -->
+  </body>
+</html>

+ 83 - 0
package.json

@@ -0,0 +1,83 @@
+{
+  "name": "mpapp",
+  "version": "1.0.0",
+  "mpvueTemplateProjectVersion": "0.1.0",
+  "description": "A Mpvue project",
+  "author": "yerong <1017503523@qq.com>",
+  "private": true,
+  "scripts": {
+    "dev:wx": "node build/dev-server.js wx",
+    "start:wx": "npm run dev:wx",
+    "build:wx": "node build/build.js wx",
+    "dev:swan": "node build/dev-server.js swan",
+    "start:swan": "npm run dev:swan",
+    "build:swan": "node build/build.js swan",
+    "dev:tt": "node build/dev-server.js tt",
+    "start:tt": "npm run dev:tt",
+    "build:tt": "node build/build.js tt",
+    "dev:my": "node build/dev-server.js my",
+    "start:my": "npm run dev:my",
+    "build:my": "node build/build.js my",
+    "dev": "node build/dev-server.js wx",
+    "start": "npm run dev",
+    "build": "node build/build.js wx"
+  },
+  "dependencies": {
+    "mpvue": "^2.0.0",
+    "vuex": "^3.0.1"
+  },
+  "devDependencies": {
+    "babel-core": "^6.22.1",
+    "babel-loader": "^7.1.1",
+    "babel-plugin-transform-runtime": "^6.22.0",
+    "babel-preset-env": "^1.3.2",
+    "babel-preset-stage-2": "^6.22.0",
+    "babel-register": "^6.22.0",
+    "chalk": "^2.4.0",
+    "connect-history-api-fallback": "^1.3.0",
+    "copy-webpack-plugin": "^4.5.1",
+    "css-loader": "^0.28.11",
+    "cssnano": "^3.10.0",
+    "eventsource-polyfill": "^0.9.6",
+    "express": "^4.16.3",
+    "extract-text-webpack-plugin": "^3.0.2",
+    "file-loader": "^1.1.11",
+    "friendly-errors-webpack-plugin": "^1.7.0",
+    "glob": "^7.1.2",
+    "html-webpack-plugin": "^3.2.0",
+    "http-proxy-middleware": "^0.18.0",
+    "optimize-css-assets-webpack-plugin": "^3.2.0",
+    "ora": "^2.0.0",
+    "portfinder": "^1.0.13",
+    "postcss-loader": "^2.1.4",
+    "postcss-mpvue-wxss": "^1.0.0",
+    "prettier": "~1.12.1",
+    "px2rpx-loader": "^0.1.10",
+    "relative": "^3.0.2",
+    "rimraf": "^2.6.0",
+    "semver": "^5.3.0",
+    "shelljs": "^0.8.1",
+    "uglifyjs-webpack-plugin": "^1.2.5",
+    "url-loader": "^1.0.1",
+    "vue-style-loader": "^4.1.0",
+    "mkdirp": "^0.5.1",
+    "mpvue-loader": "^2.0.0",
+    "mpvue-template-compiler": "^2.0.0",
+    "mpvue-webpack-target": "^1.0.3",
+    "webpack-mpvue-vendor-plugin": "^2.0.0",
+    "webpack-mpvue-asset-plugin": "^2.0.0",
+    "webpack-bundle-analyzer": "^2.2.1",
+    "webpack-dev-middleware-hard-disk": "^1.12.0",
+    "webpack-merge": "^4.1.0",
+    "webpack": "^3.11.0"
+  },
+  "engines": {
+    "node": ">= 4.0.0",
+    "npm": ">= 3.0.0"
+  },
+  "browserslist": [
+    "> 1%",
+    "last 2 versions",
+    "not ie <= 8"
+  ]
+}

+ 12 - 0
package.swan.json

@@ -0,0 +1,12 @@
+{
+  "appid": "touristappid",
+  "setting": {
+    "urlCheck": false
+  },
+  "condition": {
+    "swan": {
+      "current": -1,
+      "list": []
+    }
+  }
+}

+ 32 - 0
project.config.json

@@ -0,0 +1,32 @@
+{
+    "description": "项目配置文件。",
+    "setting": {
+        "urlCheck": true,
+        "es6": false,
+        "postcss": true,
+        "minified": true,
+        "newFeature": true
+    },
+    "miniprogramRoot": "dist/wx/",
+    "compileType": "miniprogram",
+    "appid": "touristappid",
+    "projectname": "mpapp",
+    "condition": {
+        "search": {
+            "current": -1,
+            "list": []
+        },
+        "conversation": {
+            "current": -1,
+            "list": []
+        },
+        "game": {
+            "currentL": -1,
+            "list": []
+        },
+        "miniprogram": {
+            "current": -1,
+            "list": []
+        }
+    }
+}

+ 12 - 0
project.swan.json

@@ -0,0 +1,12 @@
+{
+  "appid": "testappid",
+  "setting": {
+    "urlCheck": false
+  },
+  "condition": {
+    "swan": {
+      "current": -1,
+      "list": []
+    }
+  }
+}

+ 50 - 0
src/App.vue

@@ -0,0 +1,50 @@
+<script>
+export default {
+  created () {
+    // 调用API从本地缓存中获取数据
+    /*
+     * 平台 api 差异的处理方式:  api 方法统一挂载到 mpvue 名称空间, 平台判断通过 mpvuePlatform 特征字符串
+     * 微信:mpvue === wx, mpvuePlatform === 'wx'
+     * 头条:mpvue === tt, mpvuePlatform === 'tt'
+     * 百度:mpvue === swan, mpvuePlatform === 'swan'
+     * 支付宝(蚂蚁):mpvue === my, mpvuePlatform === 'my'
+     */
+
+    let logs
+    if (mpvuePlatform === 'my') {
+      logs = mpvue.getStorageSync({key: 'logs'}).data || []
+      logs.unshift(Date.now())
+      mpvue.setStorageSync({
+        key: 'logs',
+        data: logs
+      })
+    } else {
+      logs = mpvue.getStorageSync('logs') || []
+      logs.unshift(Date.now())
+      mpvue.setStorageSync('logs', logs)
+    }
+  },
+  log () {
+    console.log(`log at:${Date.now()}`)
+  }
+}
+</script>
+
+<style>
+.container {
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: space-between;
+  padding: 200rpx 0;
+  box-sizing: border-box;
+}
+/* this rule will be remove */
+* {
+  transition: width 2s;
+  -moz-transition: width 2s;
+  -webkit-transition: width 2s;
+  -o-transition: width 2s;
+}
+</style>

+ 44 - 0
src/app.json

@@ -0,0 +1,44 @@
+{
+  "pages": [
+    "pages/index/main",
+    "pages/logs/main",
+    "pages/counter/main"
+  ],
+  "window": {
+    "backgroundTextStyle": "light",
+    "navigationBarBackgroundColor": "#fff",
+    "navigationBarTitleText": "WeChat",
+    "navigationBarTextStyle": "black"
+  },
+  "tabBar": {
+    "color": "#999",
+    "backgroundColor": "#fafafa",
+    "selectedColor": "#333",
+    "borderStyle": "white",
+
+    "list": [{
+      "text": "首页",
+      "pagePath": "pages/index/main",
+      "iconPath": "static/tabs/home.png",
+      "selectedIconPath": "static/tabs/home-active.png"
+    }, {
+      "text": "订单",
+      "pagePath": "pages/logs/main",
+      "iconPath": "static/tabs/orders.png",
+      "selectedIconPath": "static/tabs/orders-active.png"
+    }],
+
+    "items": [{
+      "name": "首页",
+      "pagePath": "pages/index/main",
+      "icon": "static/tabs/home.png",
+      "activeIcon": "static/tabs/home-active.png"
+    }, {
+      "name": "订单",
+      "pagePath": "pages/logs/main",
+      "icon": "static/tabs/orders.png",
+      "activeIcon": "static/tabs/orders-active.png"
+    }],
+    "position": "bottom"
+  }
+}

+ 19 - 0
src/components/card.vue

@@ -0,0 +1,19 @@
+<template>
+  <div>
+    <p class="card">
+      {{text}}
+    </p>
+  </div>
+</template>
+
+<script>
+export default {
+  props: ['text']
+}
+</script>
+
+<style>
+.card {
+  padding: 10px;
+}
+</style>

+ 8 - 0
src/main.js

@@ -0,0 +1,8 @@
+import Vue from 'vue'
+import App from './App'
+
+Vue.config.productionTip = false
+App.mpType = 'app'
+
+const app = new Vue(App)
+app.$mount()

+ 44 - 0
src/pages/counter/index.vue

@@ -0,0 +1,44 @@
+<template>
+  <div class="counter-warp">
+    <p>Vuex counter:{{ count }}</p>
+    <p>
+      <button @click="increment">+</button>
+      <button @click="decrement">-</button>
+    </p>
+  </div>
+</template>
+
+<script>
+// Use Vuex
+import store from './store'
+
+export default {
+  computed: {
+    count () {
+      return store.state.count
+    }
+  },
+  methods: {
+    increment () {
+      store.commit('increment')
+    },
+    decrement () {
+      store.commit('decrement')
+    }
+  }
+}
+</script>
+
+<style>
+.counter-warp {
+  text-align: center;
+  margin-top: 100px;
+}
+.home {
+  display: inline-block;
+  margin: 100px auto;
+  padding: 5px 10px;
+  color: blue;
+  border: 1px solid blue;
+}
+</style>

+ 5 - 0
src/pages/counter/main.js

@@ -0,0 +1,5 @@
+import Vue from 'vue'
+import App from './index'
+
+const app = new Vue(App)
+app.$mount()

+ 24 - 0
src/pages/counter/store.js

@@ -0,0 +1,24 @@
+// https://vuex.vuejs.org/zh-cn/intro.html
+// make sure to call Vue.use(Vuex) if using a module system
+import Vue from 'vue'
+import Vuex from 'vuex'
+
+Vue.use(Vuex)
+
+const store = new Vuex.Store({
+  state: {
+    count: 0
+  },
+  mutations: {
+    increment: (state) => {
+      const obj = state
+      obj.count += 1
+    },
+    decrement: (state) => {
+      const obj = state
+      obj.count -= 1
+    }
+  }
+})
+
+export default store

+ 126 - 0
src/pages/index/index.vue

@@ -0,0 +1,126 @@
+<template>
+  <div @click="clickHandle">
+
+    <div class="userinfo" @click="bindViewTap">
+      <img class="userinfo-avatar" v-if="userInfo.avatarUrl" :src="userInfo.avatarUrl" background-size="cover" />
+      <img class="userinfo-avatar" src="/static/images/user.png" background-size="cover" />
+
+      <div class="userinfo-nickname">
+        <card :text="userInfo.nickName"></card>
+      </div>
+    </div>
+
+    <div class="usermotto">
+      <div class="user-motto">
+        <card :text="motto"></card>
+      </div>
+    </div>
+
+    <form class="form-container">
+      <input type="text" class="form-control" :value="motto" placeholder="v-model" />
+      <input type="text" class="form-control" v-model="motto" placeholder="v-model" />
+      <input type="text" class="form-control" v-model.lazy="motto" placeholder="v-model.lazy" />
+    </form>
+
+    <a href="/pages/counter/main" class="counter">去往Vuex示例页面</a>
+
+    <div class="all">
+        <div class="left">
+        </div>
+        <div class="right">
+        </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import card from '@/components/card'
+
+export default {
+  data () {
+    return {
+      motto: 'Hello miniprograme',
+      userInfo: {
+        nickName: 'mpvue',
+        avatarUrl: 'http://mpvue.com/assets/logo.png'
+      }
+    }
+  },
+
+  components: {
+    card
+  },
+
+  methods: {
+    bindViewTap () {
+      const url = '../logs/main'
+      if (mpvuePlatform === 'wx') {
+        mpvue.switchTab({ url })
+      } else {
+        mpvue.navigateTo({ url })
+      }
+    },
+    clickHandle (ev) {
+      console.log('clickHandle:', ev)
+      // throw {message: 'custom test'}
+    }
+  },
+
+  created () {
+    // let app = getApp()
+  }
+}
+</script>
+
+<style scoped>
+.userinfo {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
+
+.userinfo-avatar {
+  width: 128rpx;
+  height: 128rpx;
+  margin: 20rpx;
+  border-radius: 50%;
+}
+
+.userinfo-nickname {
+  color: #aaa;
+}
+
+.usermotto {
+  margin-top: 150px;
+}
+
+.form-control {
+  display: block;
+  padding: 0 12px;
+  margin-bottom: 5px;
+  border: 1px solid #ccc;
+}
+.all{
+  width:7.5rem;
+  height:1rem;
+  background-color:blue;
+}
+.all:after{
+  display:block;
+  content:'';
+  clear:both;
+}
+.left{
+  float:left;
+  width:3rem;
+  height:1rem;
+  background-color:red;
+}
+
+.right{
+  float:left;
+  width:4.5rem;
+  height:1rem;
+  background-color:green;
+}
+</style>

+ 12 - 0
src/pages/index/main.js

@@ -0,0 +1,12 @@
+import Vue from 'vue'
+import App from './index'
+
+// add this to handle exception
+Vue.config.errorHandler = function (err) {
+  if (console && console.error) {
+    console.error(err)
+  }
+}
+
+const app = new Vue(App)
+app.$mount()

+ 61 - 0
src/pages/logs/index.vue

@@ -0,0 +1,61 @@
+<template>
+  <div>
+      <swiper v-if="imgUrls.length > 0" indidator-dots="imgUrls.length > 1" >
+      <block v-for="(item, index) in imgUrls" :key="index" >
+        <swiper-item>
+          <image :src="item" mode="scaleToFill"></image>
+        </swiper-item>
+      </block>
+    </swiper>
+
+    <ul class="container log-list">
+      <li v-for="(log, index) in logs" :class="{ red: aa }" :key="index" class="log-item">
+        <card :text="(index + 1) + ' . ' + log"></card>
+      </li>
+    </ul>
+  </div>
+</template>
+
+<script>
+import { formatTime } from '@/utils/index'
+import card from '@/components/card'
+
+export default {
+  components: {
+    card
+  },
+
+  data () {
+    return {
+      logs: [],
+      imgUrls: [
+        'http://mss.sankuai.com/v1/mss_51a7233366a4427fa6132a6ce72dbe54/newsPicture/05558951-de60-49fb-b674-dd906c8897a6',
+        'http://mss.sankuai.com/v1/mss_51a7233366a4427fa6132a6ce72dbe54/coursePicture/0fbcfdf7-0040-4692-8f84-78bb21f3395d',
+        'http://mss.sankuai.com/v1/mss_51a7233366a4427fa6132a6ce72dbe54/management-school-picture/7683b32e-4e44-4b2f-9c03-c21f34320870'
+      ]
+    }
+  },
+
+  created () {
+    let logs
+    if (mpvuePlatform === 'my') {
+      logs = mpvue.getStorageSync({key: 'logs'}).data || []
+    } else {
+      logs = mpvue.getStorageSync('logs') || []
+    }
+    this.logs = logs.map(log => formatTime(new Date(log)))
+  }
+}
+</script>
+
+<style>
+.log-list {
+  display: flex;
+  flex-direction: column;
+  padding: 40rpx;
+}
+
+.log-item {
+  margin: 10rpx;
+}
+</style>

+ 5 - 0
src/pages/logs/main.js

@@ -0,0 +1,5 @@
+import Vue from 'vue'
+import App from './index'
+
+const app = new Vue(App)
+app.$mount()

+ 3 - 0
src/pages/logs/main.json

@@ -0,0 +1,3 @@
+{
+  "navigationBarTitleText": "查看启动日志"
+}

+ 24 - 0
src/utils/index.js

@@ -0,0 +1,24 @@
+function formatNumber (n) {
+  const str = n.toString()
+  return str[1] ? str : `0${str}`
+}
+
+export function formatTime (date) {
+  const year = date.getFullYear()
+  const month = date.getMonth() + 1
+  const day = date.getDate()
+
+  const hour = date.getHours()
+  const minute = date.getMinutes()
+  const second = date.getSeconds()
+
+  const t1 = [year, month, day].map(formatNumber).join('/')
+  const t2 = [hour, minute, second].map(formatNumber).join(':')
+
+  return `${t1} ${t2}`
+}
+
+export default {
+  formatNumber,
+  formatTime
+}

+ 0 - 0
static/.gitkeep


BIN
static/images/user.png


BIN
static/tabs/home-active.png


BIN
static/tabs/home.png


BIN
static/tabs/orders-active.png


BIN
static/tabs/orders.png