vue开发环境从零到一配置指南

294 阅读5分钟

前言

相信很多小伙伴在平时开发vue项目时都使用的vue cli脚手架,该脚手架已经帮忙配置好了很多基础功能,不再需要我们亲自去配置,省去了很多配置时间,但如果我们自己手动去搭建一个这样的框架,该如何搭建呢?本文主要就是介绍一下如何动手搭建一个vue2开发环境,并做一些相应的打包优化。

1、搭建webpack开发环境

新建一个项目文件夹,然后npm init,添加package.json文件,接着安装webpack4和相应的webpack-dev-server开发服务器:

yarn add -D webpack webpack-cli // 我采用的webpack@4.46.0 webpack-cli@4.9.2
yarn add -D webpack-dev-server // webpack-dev-server@4.8.1

在src目录下添加入口文件main.js,并在build目录下新建webpack.config.js打包配置文件,并做如下配置:

// webpack.config.js
const path = require('path')
module.exports = {
  entry: path.resolve(__dirname, '../src/main.js'),
  output: {
    path: path.resolve(__dirname, '../dist'),
    // 生成的 js 文件名称
    filename: 'app.js'
  },
  module: {
    //
  }
}

添加运行脚本即可进行基本的js打包操作:

"scripts": {
  "dev": "webpack --mode development --config build/webpack.config.js"
}

2、配置babel

在对js文件进行打包时,我们还需要转换一些es6语法,有些浏览器对新的es语法不是很支持,这时候就需要利用babel对源码进行转换操作。 安装babel-loader、@babel/core(babel核心功能模块):

yarn add -D babel-loader@8.2.4 @babel/core@7.17.8 @babel/preset-env@7.16.11

安装完以后,就可以配置babel-loader来转换js语法为浏览器通用es5语法,但对于新的api语法还是不能转换,需要单独引入polyfill。

好在@babel/preset-env提供了一个useBuiltIns参数,当将其值设为usage时,就可以按需加载polyfill。在设置该值的同时,还需要安装core-js@3,支持最新语法。

yarn add core-js

安装完依赖以后在根目录添加配置文件babel.config.js,并配置相应的webpack.config.js:

// babel.config.js
module.exports = {
  exclude: [
    /node_modules/
  ],
  presets: [
    [
      // 转换es6等新特性代码
      '@babel/preset-env',
      {
        useBuiltIns: "usage", // 按需引入ployfill
        corejs: 3
      }
    ]
  ],
  plugins: [
    //
  ]
}

// webpack.config.js
module.exports = {
  entry: path.resolve(__dirname, '../src/main.js'),
  output: {
    path: path.resolve(__dirname, '../dist'),
    // 生成的 js 文件名称
    filename: 'app.js'
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        loader: "babel-loader"
      }
    ]
  }
}

3、配置vue loader

安装vue和识别vue单文件组件的webpack loader、模板编译器:

yarn add -D vue-loader@15.9.8 vue-template-compiler@2.6.14
yarn add vue@2.6.14

并做如下配置:

// webpack.config.js
const { VueLoaderPlugin } = require('vue-loader')
module.exports = {
  entry: './src/main.js',
  output: {
    path: path.resolve(__dirname, '../dist'),
    // 生成的 js 文件名称
    filename: 'app.js'
  },
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      }
    ]
  },
  resolve: {
    //引入路径不用写后缀名
    extensions: ['.js', '.vue', '.json'],
    //缩写扩展
    alias: {
      //用@直接指引到src目录下
      '@': path.resolve(__dirname, './src')
    }
  },
  plugins: [
    new VueLoaderPlugin()
  ]
}

// main.js
import Vue from 'vue'
import App from './App'

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

4、添加vue-router和vuex

yarn add vue-router@3.5.3 vuex@3.6.2

在src目录下添加router和store目录,并作如下配置:

// route/index.js
import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)

export default new Router({
  mode: 'history',
  base: process.env.NODE_ENV === 'production' ? '/webpack-vue/' : '/', // 根据部署路径进行设置
  routes: [
    {
      path: '/',
      name: 'HelloVue',
      component: () =>
        import(/* webpackChunkName: "HelloVue" */ '../views/HelloWorld') // webpack魔法注释设置chunkName,代码分割,按需引入
    },
    {
      path: '/test',
      name: 'Test',
      component: () => import(/* webpackChunkName: "Test" */ '../views/test')
    }
  ]
})

//store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import app from './app'
Vue.use(Vuex)

export default new Vuex.Store({
  modules: {
    app
  }
})

// main.js
import router from './router'
import store from './store'
new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

5、配置html-webpack-plugin

当代码打包完成后可以自动生成html文件,或者以现有的html文件为模板生成一个,同时将各种类型bundle嵌入html当中,只需在webpack配置中添加该插件即可:

const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports= {
  ...
  plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, '../public/index.html')
    })
  ]
}

该插件还能设置一些变量,在模板index.html中使用:

module.exports= {
  ...
  plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, '../public/index.html'),
      testVal: 'testetsttest'
    })
  ]
}

//public/index.html
<body>
  <%= htmlWebpackPlugin.options.testVal %>
  <div id="app">
  </div>
</body>

6、sass

安装sass-loader和node-sass预处理样式:

yarn add -D sass-loader@7.2.0 node-sass@4.14.1

除此之外还要安装css-loader和style-loader,css-loader解析css文件,style-loader主要是将css解析到html页面的style上:

yarn add -D css-loader@2.0 style-loader@2.0.0

7、postcss-loader

安装postcss-loader和autoprefixer实现自动添加css3前缀:

yarn add -D postcss-loader@4.3.0 autoprefixer@8.6.5

添加postcss.config.js,在package.json文件中添加browserslist,配置适配的浏览器范围,也可以在插件对象中添加browserslist属性:

// postcss.config.js
module.exports = {
  plugins: {
    autoprefixer: {
      // browsers: [
      //   'last 10 Chrome versions',
      //   'last 5 Firefox versions',
      //   'Safari >= 6', 
      //   'ie > 8'
      // ] 
    }
  }
}

"browserslist": [
  "> 1%",
  "last 2 versions",
  "Safari >= 6",
  "ie > 8"
]

第6步和第7步的相关配置文件如下:

module.exports= {
  modules: {
    rules: [
      {
        test: /\.(scss|sass|css)$/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              importLoaders: 2 // 如果css中使用@import语句,就先采用前面两个loader处理@import模块
            }
          },
          'postcss-loader',
          'sass-loader'
        ]
      }
    ]
  }
}

8、devServer

配置webpack开发服务器,具体配置如下:

module.exports = {
  devServer: {
    hot: true, // 开启热更新
    port: 8000, // 端口号
    historyApiFallback: true, // 针对vue路由模式为history时,请求不同路径返回html
    open: true // 构建完自动打开浏览器
  },
  devtool: 'eval-cheap-module-source-map' // 开启source-map
}

9、配置webpack打包图片、文字等文件

添加webpack file-loader或url-loader,file-loader可以解析文件url,url-loader有file-loader的能力,同时可以把小于一定大小的文件转换成base64,内联到代码中,安装配置如下:

yarn add -D url-loader@4.1.1

module.exports = {
  module: {
    rules: [
      {
        test: /\.(png|jpe?j|gif|svg)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          esModule: false,
          name: process.env.NODE_ENV === 'production' ? 'img/[name].[hash].[ext]' : 'img/[name].[ext]'
        }
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
        loader: "url-loader",
        options: {
          limit: 10000,
          name: process.env.NODE_ENV === 'production' ? 'fonts/[name].[hash].[ext]' : 'fonts/[name].[ext]'
        }
      }
    ]
  }
}

10、配置环境变量

不同的环境变量可以使用不同的打包文件,比如开发环境使用快速方便的打包配置文件,不需要压缩代码、需要热更新、不需要抽离css文件、开启source-map等等,而生产环境需要压缩代码、提取css文件,打包使用chunkhash和contenthash等。 可以通过安装cross-env来跨平台设置环境变量:

yarn add -D cross-env

//添加npm script运行不同的脚本
"dev": "cross-env NODE_ENV=development webpack-dev-server --mode development --config build/webpack.config.js",
"build": "cross-env NODE_ENV=production webpack --mode production --config build/webpack.config.js"

在项目根目录的build文件夹中分别添加webpack.base.config.js、webpack.dev.config.js、webpack.prod.config.js、webpack.config.js。webpack.base.config.js当中维护公共的打包配置,dev和prod分别维护开发和生产的打包配置,webpack.config.js用来根据不同环境变量合并选择相应的打包配置。

const { merge } = require('webpack-merge') // 合并打包配置
const baseConfig = require('./webpack.base.config')
const devConfig = require('./webpack.dev.config')
const prodConfig = require('./webpack.prod.config')

module.exports = (env, argv) => {
  console.log(argv)
  const config = process.env.NODE_ENV == 'development' ? devConfig : prodConfig
  return merge(baseConfig, config) 
}

11、hash、chunkhash、contenthash

hash一般是结合CDN缓存来使用,通过webpack构建之后,生成对应文件名自动带上对应的MD5值。如果文件内容改变的话,那么对应文件哈希值也会改变,对应的HTML引用的URL地址也会改变,触发CDN服务器从源服务器上拉取对应数据,进而更新本地缓存。

hash是跟整个项目的构建打包有关,只要项目中某个文件有改动,整个项目构建的hash值都会改变。

chunkhash和hash不太一样,它根据入口文件解析、构建对应的chunk,生成对应的哈希值。我们在生产环境里把一些公共库和程序入口文件区分开,单独打包构建,接着我们采用chunkhash的方式生成哈希值,那么只要我们不改动公共库的代码,就可以保证其哈希值不会受影响。

如果都使用chunkhash的话,css文件单独抽离时,如果改动了引入这个css文件的js文件后,css文件使用chunkhash也会跟着改动,这其实并不是我们想要的,这时候可以针对css文件使用contenthash,contenthash和内容相关,内容不变就不会改变。

module.exports = {
  entry: path.resolve(__dirname, '../src/main.js'),
  output: {
    path: path.resolve(__dirname, '../dist'),
    // 生成的 js 文件名称
    filename: process.env.NODE_ENV == 'production' ? 'app.[chunkhash:8].js' : 'app.js',
    // 生成的 chunk 名称
    chunkFilename: process.env.NODE_ENV == 'production' ? '[name]/[name].[chunkhash:8].js' : '[name]/[name].js'
  }
}

12、css样式压缩和抽离

配置mini-css-extract-plugin插件抽离css样式,配置optimize-css-assets-webpack-plugin插件进行css压缩。 安装mini-css-extract-plugin:

yarn add -D mini-css-extract-plugin@1.6.2

安装optimize-css-assets-webpack-plugin,同时还需要搭配cssnano使用:

yarn add -D optimize-css-assets-webpack-plugin@4.0.3 cssnano@4.1.7

webpack配置如下:

const MiniCssExtractPlugin = require("mini-css-extract-plugin") // 抽离css到单独文件中
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') // css压缩
module.exports = {
  module: {
    rules: [
      {
        test: /\.(scss|sass|css)$/,
        use: [
          MiniCssExtractPlugin.loader,
          {
            loader: 'css-loader',
            options: {
              importLoaders: 2
            }
          },
          'postcss-loader',
          'sass-loader'
        ]
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: 'css/[name].[contenthash:8].css'
    }),
    new OptimizeCSSPlugin({
      assetNameRegExp: /\.css$/g,
      cssProcessor: require('cssnano')
    })
  ]
}

13、定义全局打包可替换变量

利用webpack DefinePlugin可以定义全局的打包可替换变量,在打包处理时webpack会自动识别该变量,具体配置如下:

const CopyWebpackPlugin = require('copy-webpack-plugin')
const webpack = require('webpack')

module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      template: './public/index.html',
      testVal: 'testetsttest'
    }),
    new VueLoaderPlugin(),
    new CopyWebpackPlugin([{ // 将public文件目录直接copy到打包最终文件中
      from: 'public'
    }]),
    // 定义全局打包可替换变量
    new webpack.DefinePlugin({
      BASE_URL: process.env.NODE_ENV === 'production' ? JSON.stringify('https://youchen133.github.io/webpack-vue/') : JSON.stringify('/'),
      SOMETHING: JSON.stringify('this is something!')
    })
  ]
}

定义的变量可以直接在html中使用:

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no">
  <title>vue cli框架实现</title>
  <script src="<%= BASE_URL %>lib/vue.runtime.min.js"></script>
  <script src="<%= BASE_URL %>lib/vue-router.min.js"></script>
  <script src="<%= BASE_URL %>lib/vuex.min.js"></script>
</head>
<body>
  111
  <%= htmlWebpackPlugin.options.testVal %>
  <%= SOMETHING %>
  <div id="app">
  </div>
</body>

14、webpack优化之externals

当我们打包时,vue、vuex和vue-router等基础库也会打包到最终的bundle文件当中,这会导致整个的bundle体积比较大,影响加载时间,而且也影响打包速度,其实可以采用直接在html引入cdn链接的方式解决这个问题,这时候就需要配置webpack externals,使得基础库不参与打包。

在public文件夹目录下添加基础库官方最终打包好的生产版本vuex.min.js、vue.runtime.min.js、vue-router.min.js,可以直接在html中引入,然后配置webpack externals属性:

module.exports = {
  // 排除文件引入打包,直接在html中引入
  externals: {
    'vue': 'Vue',
    'vuex': 'Vuex',
    'vue-router': 'VueRouter'
  }
}

15、配置eslint和prettier

在开发过程中配置eslint和prettier统一代码风格,具体安装步骤如下:

安装eslint-loader、eslint:

yarn add -D eslint-loader@4.0.2 eslint@7.32.0

添加vue-eslint-parser解析vue文件,同时安装@babel/eslint-parser,配合vue-eslint-parser,解析一些最新的es6语法:

yarn add -D @babel/eslint-parser@7.17.0 vue-eslint-parser@8.3.0

安装eslint-plugin-vue、eslint-plugin-prettier、eslint-config-prettier插件:

yarn add -D eslint-plugin-vue@7.20.0 eslint-plugin-prettier@4.0.0 eslint-config-prettier@8.5.0

eslint-plugin-vue是对.vue 文件进行代码校验的插件,同时扩展了一些vue解析语法。 eslint-plugin-prettier先调用prettier对你的代码进行格式化,然后会把格式化前后不一致的地方进行标记,通过配置 'prettier/prettier': 'error' 此条规则会将标记地方进行 error 级别的报错提示。为了防止冲突,可以配置eslint-config-prettier。 项目根目录添加.eslintrc.js和.prettierrc.js文件:

// .eslintrc
module.exports = {
  root: true,
  env: {
    browser: true,
    es6: true,
    node: true
  },
  globals: {
    SOMETHING: true // 防止全局打包替换变量eslint检测报错
  },
  parser: 'vue-eslint-parser', // 识别vue文件
  parserOptions: {
    sourceType: 'module',
    parser: '@babel/eslint-parser' // 配合vue-eslint-parser,解析一些最新的es6语法
  },
  extends: [
    'eslint:recommended',
    'plugin:vue/essential',
    'prettier'
  ],
  plugins: [
    'vue',
    'prettier'
  ],
  rules: {
    'prettier/prettier': 'error',
    //强制使用单引号
    quotes: ['error', 'single'],
    //强制不使用分号结尾
    semi: ['error', 'never']
  }
}

//.prettierrc
module.exports = {
  printWidth: 80,                   
  tabWidth: 2,                      
  useTabs: true,                    
  semi: false,                       
  singleQuote: true,                
  ...
}

16、总结

以上就是本次文章的全部内容,还有其它的一些webpack打包构建优化方法,大家也可以去查找相关资料并进行实际尝试一下,本次的项目示例链接为github.com/youchen133/…

参考文章: