使用 Vue-Cli4 / Webpack5 / Vue2 / Typescript / Vant / postcss-pxtorem 构建移动端项目

1,629 阅读4分钟

最近由于业务需要,要重新开始构建一个 vue 的 H5 项目

我这个人一直很喜欢用新版本的东西,所以 Vue-Cli4 Webpack5 Typescript 通通安排上!!!

为什么不用 Vue3 呢,因为个人感觉这 3.x 版本现在还不太适合投入生产 【我绝对不会承认是因为自己菜学不动 Vue3的....】

我对这个项目的期望是有如下几点:

  1. 使用 Vue-cli4 / Webpack5 来构建项目
  2. 引入 Typescript
  3. 引入 Eslint
  4. 使用 gzip / image 压缩等
  5. 自适应布局 amfe-flexible / postcss-pxtorem
  6. 引入 vant-ui,并配置按需引入

So! 下面开始进入正题吧!

初始化项目

安装 Vue-cli4

首先我们需要用 vue -V 这个命令行查看本机的是否安装了 vue-cli,或者 vue-cli 的版本是否为 4.x 版本

如果已经安装,但是版本太低了就删掉重新安装就好啦

npm uninstall vue-cli -g

然后再次

npm install @vue/cli -g 

安装完成后再次使用 vue -V命令查看版本,4.x 的版本就可以继续下面的操作啦。

初始化项目

vue create #你的项目名#

使用 vue 的 create 命令来构建项目,比如 vue create my-app

接下来就会出现自定义配置的选项啦~

首先这里我们选择第三个:Manually select features

然后勾选自己需要的模块,我勾选的是:

Babel / TypeScript / Router / Vuex / CSS Pre-processors / Lint

如上图所示~

接下来就是让你选择你想使用的 vue 的版本【我卑微的选择了 2.x】

然后是选择路由的模式,不选择 History 模式的原因是找后端配置太麻烦啦,自己搞定比较好

最后一个问题:是否保留本次的配置,这样下次初始化项目的时候可以直接使用,我选择的不【毕竟下一次新建项目估计就是有新玩意儿了呢~~~

好啦,这样我们就初始化好啦~

进入项目运行起来就能看到熟悉的欢迎页~

依赖安装

自 vue-cli 的 3.x 和 webpack4.x 版本后,就不再有各种配置文件啦,取而代之是一个 vue.config.js 文件

由于需要增加 pxtorem 的配置和 vant 组件的按需引入,以及各种压缩插件,我会在后面直接献上最终的配置文件:

首先我们需要安装各种依赖啦:

// 自适应布局的
npm install postcss-pxtorem -D
npm install amfe-flexible

// 压缩图片的和各种 loader
cnpm install image-webpack-loader  --save-dev
cnpm install pngquant-bin --save-dev
npm install imagemin-gifsicle --save-dev
npm install imagemin-mozjpeg --save-dev
npm install imagemin-optipng --save-dev
npm install imagemin-pngquant --save-dev
npm install compression-webpack-plugin --save-dev
//  注意这里的 image-webpack-loader 和 pngquant-bin 一定要使用 cnpm 安装,使用 npm 会导致项目报错无法运行

//  安装 vant ui 和它的按需引入插件

npm i vant -S
// 这个是按需引入的插件
npm i ts-import-plugin --save-dev 

好啦,各种依赖安装完毕,接下来就是文件配置了:

文件配置

vue.config.js

vue.config.js

// vue.config.js
const path = require('path');
const CompressionWebpackPlugin = require('compression-webpack-plugin'); // 开启gzip压缩, 按需引用
const productionGzipExtensions = /\.(js|css|json|txt|html|ico|svg)(\?.*)?$/i; // 开启gzip压缩, 按需写入
const merge = require('webpack-merge');
const tsImportPluginFactory = require('ts-import-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer')
  .BundleAnalyzerPlugin; // 打包分析
const IS_PROD = ['production', 'prod'].includes(process.env.NODE_ENV);
const resolve = dir => path.join(__dirname, dir);
module.exports = {
  publicPath: process.env.NODE_ENV === 'production' ? '/site/vue-demo/' : '/',
  // 公共路径
  indexPath: 'index.html',
  // 相对于打包路径index.html的路径
  outputDir: process.env.outputDir || 'dist',
  // 'dist', 生产环境构建文件的目录
  assetsDir: 'static',
  // 相对于outputDir的静态资源(js、css、img、fonts)目录
  lintOnSave: false,
  // 是否在开发环境下通过 eslint-loader 在每次保存时 lint 代码
  runtimeCompiler: true,
  // 是否使用包含运行时编译器的 Vue 构建版本
  productionSourceMap: !IS_PROD,
  // 生产环境的 source map
  // parallel: require('os').cpus().length > 1,
  parallel: false,
  // 是否为 Babel 或 TypeScript 使用 thread-loader。该选项在系统的 CPU 有多于一个内核时自动启用,仅作用于生产构建。
  pwa: {},
  chainWebpack: config => {
    config.resolve.symlinks(true); // 修复热更新失效
    // 如果使用多页面打包,使用vue inspect --plugins查看html是否在结果数组中
    config.plugin('html').tap(args => {
      // 修复 Lazy loading routes Error
      args[0].chunksSortMode = 'none';
      return args;
    });
    config.resolve.alias // 添加别名
      .set('@', resolve('src'))
      .set('@assets', resolve('src/assets'))
      .set('@components', resolve('src/components'))
      .set('@views', resolve('src/views'))
      .set('@store', resolve('src/store'));
    // 压缩图片
    // 需要 npm i -D image-webpack-loader
    config.module
      .rule('images')
      .use('image-webpack-loader')
      .loader('image-webpack-loader')
      .options({
        bypassOnDebug: true
      }).end();
    config.module
      .rule('ts')
      .use('ts-loader')
      .tap(options => {
        options = merge(options, {
          happyPackMode: true,
          transpileOnly: true,
          getCustomTransformers: () => ({
            before: [
              tsImportPluginFactory({
                libraryName: 'vant',
                libraryDirectory: 'es',
                // 这句必须加上,不然修改主题没有效果
                style: name => `${name}/style/less`
              })
            ]
          }),
          compilerOptions: {
            module: 'es2015'
          }
        })
        return options
      });
    // 打包分析, 打包之后自动生成一个名叫report.html文件(可忽视)
    if (IS_PROD) {
      config.plugin('webpack-report').use(BundleAnalyzerPlugin, [{
        analyzerMode: 'static'
      }]);
    }
  },
  configureWebpack: config => {
    // 开启 gzip 压缩
    // 需要 npm i -D compression-webpack-plugin
    const plugins = [];
    if (IS_PROD) {
      plugins.push(
        new CompressionWebpackPlugin({
          filename: '[path].gz[query]',
          algorithm: 'gzip',
          test: productionGzipExtensions,
          threshold: 10240,
          minRatio: 0.8
        })
      );
    }
    config.plugins = [...config.plugins, ...plugins];
  },
  css: {
    extract: IS_PROD,
    requireModuleExtension: true, // 若为 false,可能导致按需引入的 vant 样式不生效
    loaderOptions: {
      less: {
        // `globalVars` 定义全局对象,可加入全局变量
        globalVars: {
          primary: '#333'
        },
      },
      postcss: {
        plugins: [
          require('autoprefixer')({
            // 配置使用 autoprefixer
            overrideBrowserslist: ['last 15 versions']
          }),
          //如果个别地方不想转化px。可以简单的使用大写的 PX 或 Px 。
          require('postcss-pxtorem')({
            rootValue: 37.5, // 换算的基数
            propList: ['*'],
            //exclude: /node_modules/  //配置无需转换
          })
        ]
      }
    }
  },
  devServer: {
    overlay: {
      // 让浏览器 overlay 同时显示警告和错误
      warnings: true,
      errors: true
    },
    host: 'localhost',
    port: 8080,
    // 端口号
    https: false, // https:{type:Boolean}
    open: false, //配置自动启动浏览器
    hotOnly: true, // 热更新
    proxy: {
      //配置多个跨域
      // '/api': {
      //   target: 'http://xxx.xxx',
      //   changeOrigin: true,
      //   // ws: true,//websocket支持
      //   secure: false,
      //   pathRewrite: {
      //     '^/api': '/'
      //   }
      // },
    }
  }
};

直接复制进去就可以,每个字段配置都有相应的解释,如果碰到说有依赖缺失就再次 install 就好啦

tsconfig.json

tsconfig.json

{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "strict": true,
    "jsx": "preserve",
    "importHelpers": true,
    "moduleResolution": "node",
    "experimentalDecorators": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "sourceMap": true,
    "baseUrl": ".",
    "types": [
      "webpack-env"
    ],
    "paths": {
      "@/*": [
        "src/*"
      ]
    },
    "lib": [
      "esnext",
      "dom",
      "dom.iterable",
      "scripthost"
    ]
  },
  "include": [
    "src/**/*.ts",
    "src/**/*.tsx",
    "src/**/*.vue",
    "tests/**/*.ts",
    "tests/**/*.tsx"
  ],
  "exclude": [
    "node_modules"
  ]
}

babel.config.js

babel.config.js

module.exports = {
  presets: [
    '@vue/cli-plugin-babel/preset'
  ],
  plugins: [
    ['import', {
      libraryName: 'vant',
      libraryDirectory: 'es',
      style: true
    }, 'vant']
  ]
}

main.ts

main.ts

import Vue from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
import 'amfe-flexible/index.js'; // flex布局

Vue.config.productionTip = false;

new Vue({
	router,
	store,
	render: (h) => h(App),
}).$mount('#app');

这样就差不多配置好啦!!

我的项目结构和 package.json如下图所示:

这样子一个项目就基本搭建好啦,如果中途又遇到什么问题欢迎评论一起讨论哦