webpack4 从零搭建 vue 项目开发环境

1,955 阅读6分钟

前言

我相信不少朋友和我一样,刚开始学习 vue 的时候,都是用 vue-cli创建的项目,vue-cli确实是个好东西,让我们不需要关心 webpack 等一些繁杂的配置。 然后直接开始写业务代码。但这会造成我们过度依赖 vue-cli,忽视了 webpack 的重要性,当遇到一些特殊场景时候,例如:vue 多入口的配置、优化项目的打包速度等等。便无从下手。

vue-cli提供了修改webpack配置的入口——vue.config.js。即使你熟悉webpack,但你不知道vue-cli内部配置了什么,也很难下手。所以有些公司会选择自己手动搭建,自己配置了哪些东西,心里有数。便于维护。

所以无论如何,学好webpack准没错啦 !

webpack 5正式版是 2020-10 发布的,相信很多公司用的还是 webpack 4。所以这篇文章就先介绍 webpack 4

安装时候注意依赖的版本,最好带上版本号,下面是依赖的版本。否则会出现不兼容的情况

  "devDependencies": {
    "autoprefixer": "^10.3.1",
    "babel-loader": "^8.2.2",
    "css-loader": "^5.2.7",
    "file-loader": "^6.2.0",
    "html-webpack-plugin": "^4.5.2",
    "less": "^3.5.0",
    "less-loader": "^7.3.0",
    "postcss-loader": "^4.3.0",
    "style-loader": "^2.0.0",
    "url-loader": "^4.1.1",
    "vue-loader": "^15.9.6",
    "vue-template-compiler": "^2.6.14",
    "webpack": "^4.46.0",
    "webpack-cli": "^3.3.11",
    "webpack-dev-server": "^3.11.2",
  },
  "dependencies": {
    "vue": "^2.6.14",
    "vue-router": "^3.5.2",
    "vuex": "^3.6.2"
  }

续集:

文章 demo 已发送到 github 请自行下载,】「demo 包括了第二篇的文章内容,推荐看完第二篇」 —— 「demo」

看要是对你有帮助,麻烦点个赞~ 谢谢。

npm 初始化

在你的根目录上运行 npm init,生成 package.json

npm init

安装 webpack

npm默认安装的是最新版本的webpack,所以要指定版本号。webpack 4以上版本需要安装 webpack-cli ,也要指定版本。

npm i -D webpack@4.46.0 webpack-cli@3.3.11

测试 webpack 是否安装成功

我们通过简单打包方式测试 webpack 是否安装成功

src中创建一个 main.js,随便写个console

//main.js
console.log('我只会 webpack 打包,你公司还要人吗?')

package.json 配置 npm 脚本命令

//实际项目中不建议在脚本命令上添加参数
"scripts": {
    "build": "webpack src/main.js -o dist/bundle.js" 
},

然后运行 npm run build 如果看到 dist 中生成了 bundle.js 就说明 OK了。你可以在 bundle.js中找到 main.js 的内容

很多朋友会想到在 terminal(终端) 中直接运行不就行了,为什么还要配置 npm 脚本?因为直接在 terminal运行,用的是你全局安装的 webpack 版本,和你项目中的 webpack有差异。如果你全局没有安装 webpack 那就会直接报错。

webpack 一些基本认知

loader

webpack默认情况下只支持js、json格式的文件。所以要把css、img、html、vue等等这些文件转换成 js,这样webpack才能识别,这时候就要用到webpack中的loader,我们可以把loader理解成转换器

plugin

webpack在打包的过程中,会在特定的节点执行一些函数,类似 vue一样生命周期函数。plugin可以通过这些监听函数,执行一些webpack本身之外操作。例如:压缩图片优化打包 等等

配置 webpack 支持 less「css 包含在里面」

安装 loader

为了便于维护,项目中往往会使用 css 预处理器 ——Sass、Less 或者 Stylus,这里以Less为例,其他的类似,请自行百度

  • style-loader,把 CSS 插入到 DOM
  • css-loader , 解析 CSS 文件中的 @importurl ,处理 css-modulescss 转换 js
  • less-loader,把 less 转换成 css
  • less, 让项目支持 less
npm install less@3.5.0 less-loader@7.3.0 css-loader@5.2.7 style-loader@2.0.0 --save-dev

配置 webpack.config.js

根目录新建 webpack.config.js,这是 webpack的配置文件

const path = require('path');
module.exports = {
	mode: "development",
	entry: "/src/main.js", //入口
	output: {
		path: path.join(__dirname, "./dist"), //打包输出路径,必须是绝对路径
		filename: "bundle.js" //打包之后的 JS 名称
	},
	module: {
		rules: [
                         // 处理 css 
			{
				test: /\.(css)$/,
				use: [
					"style-loader",
					"css-loader"
				]
			},
                        // 处理 less 
			{
				test: /\.(less)$/,
				use: [
					"style-loader",
					"css-loader",
					"less-loader"
				]
			},
                        // 处理 less 和 css ,不推荐使用,文件为CSS 时,虽然也可以处理,但是速度会比较慢(css文件大的时候特别明显)。因为需要先经过 less-loader 的处理,多了个步骤
                        // {
			// 	test: /\.(less|css)$/,
			// 	use: [
			// 		"style-loader",
			// 		"css-loader",
			// 		"less-loader"
			// 	]
			// },
		]
	}
};

修改 npm 脚本

"scripts": {
    "build": "webpack" 
},

测试 less 是否生效

新建 index.less , 随便在写点样式。

div {
    width:100px;
    height:100px;
    background:red
}

在 main.js 中引入 less 文件,运行 npm run build进行打包

import "./css/index.less"

dist目录下新建一个 html ,并引入打包后的 bundle.js, 看看样式是否生效。

<body>
    <div>less样式生效</div>
    <script src="./bundle.js"></script>
</body>

使用 html-webpack-plugin ,生成html,自动引入 bundle.js

大家应该注意到,vue-cli 时候项目打包的时候会自己在 dist 目录下生成一个html文件,并引入bundle.js,就是通过html-webpack-plugin实现的。

安装 html-webpack-plugin

npm i html-webpack-plugin@4.5.2 -D

webpack.config.js 配置 HtmlwebpackPlugin

const HtmlwebpackPlugin = require('html-webpack-plugin')
plugins: [
	new HtmlwebpackPlugin({
                //配置 html 生成模版
		template: path.join(__dirname, './src/index.html')
	})
]

webpack-dev-server

我们平时开发项目,预览效果时,一般直接访问某个ip + 端口进行调试的。webpack-dev-server就是用来帮我们实现这个功能,它实际上是基于express「NodeJS框架」来实现 web服务器 的功能。

注意:webpack-dev-server 打包之后的 html 和 bundle.js 是放在内存中的,目录里是看不到的,一般会配合 webpack 的热更新模块来使用

安装 webpack-dev-server

npm i webpack-dev-server@3.11.2

webpack.config.js 配置 devServer

devServer: {
    contentBase: path.join(__dirname, "./dist"), //打包之后的目录
    open: true, //打包完成自动打开浏览器预览
    quiet: true, //隐藏在 terminal中显示打包信息
    progress: true, //显示打包进度 
    port: 3000  //不指定端口会自动分配
},

package.json 添加 npm 脚本

serve 为新加的 npm 脚本 , 运行 npm run serve就可以预览项目了

  "scripts": {
    "serve": "webpack-dev-server",
    "build": "webpack"
  },

HotModuleReplacementPlugin 热更新

HotModuleReplacementPluginwebpack内置的模块,不需要额外安装,可以配合webpack-dev-server实现热更新的功能

webpack.config.js 配置 devServer

const webpack = require("webpack")

devServer: {
    hot: true, //开启热更新
    clientLogLevel: 'none',  //关闭浏览器控制台输出的热更新信息
},

plugins: [
    new webpack.HotModuleReplacementPlugin() 
]

测试热更新

main.js 里加个 console.log 在控制台能看到对应的信息就可以了

配置 webpack 支持打包 字体、图片等文件

安装 url-loader 和 file-loader

url-loader 解析为 url 并将文件复制到输出目录中, 还支持 base64 图片格式的转换

  • url-loader 把文件转成 bas64
  • file-loader 把文件复制到指定的目录
npm i style-loader@2.0.0 url-loader@4.1.1 -D

配置 loader

rules: [
    {
        test: /\.(png|jpg|gif)$/i,
        use: [
            {
                loader: 'url-loader',
                options: {
                    limit: 8192, //小于 8K ,用 url-loader 转成 base64 ,否则使用 file-loader 来处理文件
                    fallback: {
                        loader: 'file-loader',
                        options: {
                            name: '[name].[hash:8].[ext]',
                            outputPath: 'images/', //打包之后文件存放的路径, dist/images
                            // publicPath: 'assets/' // 拼接在url的目录,不设置默认使用 outputPath 的路径
                        }
                        // 路径也可以直接写在 name 上,和上面效果一致
                        //  options: {
                        // 	 name: 'images/[name].[hash:8].[ext]',
                        //  }
                    },

                }
            }
        ]
    },
    // 媒体文件
    {
        test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
        use: [
            {
                loader: 'url-loader',
                options: {
                    limit: 8192,
                    fallback: {
                        loader: 'file-loader',
                        options: {
                            name: '[name].[hash:8].[ext]',
                            outputPath: 'media/',
                        }
                    },

                }
            }
        ]
    },
    //字体文件
    {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i,
        use: [
            {
                loader: 'url-loader',
                options: {
                    limit: 1,
                    fallback: {
                        loader: 'file-loader',
                        options: {
                            name: '[name].[hash:8].[ext]',
                            outputPath: 'fonts/',
                        }
                    },

                }
            }
        ]
    }
]

测试 loader 是否生效

在样式里加个背景图片,调整 limit 的阈值,查看是否有转换成 base64 ,如果可以那就是说明url-loader没有问题,要是在dist里生成了images/xxx.xx,就说明file-loader生效了。

配置 webpack 支持 vue

安装依赖

  • vue vue 官方依赖包
  • vue-loader 解析 .vue 文件
  • vue-template-compiler 解析 .vue 文件里面的 template,配合 vue-loader使用

安装 vue

npm i vue@2.6.14 -S

安装 loader

npm i vue-loader vue-template-compiler -D

webpack.config.js 配置 vue-loader

const VueLoaderPlugin = require('vue-loader/lib/plugin')

module: {
    rules: [
        {
            test: /\.vue$/,
            use: [
                {
                    loader: "vue-loader"
                }
            ]
        }
    ],
    plugins: [
        new VueLoaderPlugin()
    ]
}

测试 vue 是否可以正常编译

index.html添加 vue 挂载的 DOM 元素 (#app)

<body>
    <div id="app"></div>
</body>

新建 App.vue ,写一个简单的双向绑定例子

<template>
  <div>
    <div class="text">{{ text }}</div>
    <input type="text" v-model="text" />
  </div>
</template>

<script>
export default {
  data() {
    return {
      text: '不是阿怪的阿怪'
    }
  },
  methods: {}
}
</script>

<style lang="less" scoped>
.text {
  color: red;
}
</style>

main.js 导入 vue 并挂载

import Vue from "vue"
import App from "./App.vue";

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

能编译过基本就没有问题了,测试一下双向绑定正常就 OK

Vue Router

安装 vue-router

npm i vue-router@3.5.2 -S

新增 views/Home.vue

<template >
  <div>
    <div>Vue Router 搞定</div>
  </div>
</template>

新建 router/index.js

import VueRouter from "vue-router"
import Vue from "vue"

Vue.use(VueRouter)

const routes = [{
	path: '/',
	name: 'Home',
	component: () => import('../views/Home.vue')
}]

const router = new VueRouter({
	routes
})

export default router

App.vue 加上 <router-view/>

<template>
  <div>
    <router-view />
  </div>
</template>

main.js 导入 router/index.js, 并使用

import Vue from "vue"
import App from "./App.vue";
import router from "./router/index"; //关键代码

new Vue({
	router, //关键代码
	render: (h) => h(App)
}).$mount("#app")

运行预览效果,能看到 Vue Router 搞定 几个字就证明 vue-router OK 了

Vuex

安装 vuex

npm i vuex@3.6.2 -S

新建 store/index.js

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)
const state = {
	text: '默认值'
}

const getters = {
	getText(state) {
		return state.text
	}
}
const actions = {
	setText: ({ commit }, text) => {
		return commit('setText', text)
	}
}
const mutations = {
	setText: (state, text) => {
		state.text = text
	}
}

export default new Vuex.Store({
	state,
	actions,
	mutations,
	getters
})

main.js导入store/index.js,并使用

import Vue from "vue"
import App from "./App.vue";
import router from "./router/index";
import store from "./store"; //关键代码

new Vue({
	store, //关键代码
	router,
	render: (h) => h(App)
}).$mount("#app")

Home.vue 中使用 vuex

<template >
  <div>
    <div>'vuex-state:'{{ text }}</div>
    <div>'vuex-getter:'{{ getText }}</div>
    <button @click="setText(123)">修改state的值</button>
  </div>
</template>
<script>
import { mapGetters, mapState, mapActions } from "vuex"
export default {
  data() {
    return {

    }
  },
  computed: {
    ...mapState(['text']),
    ...mapGetters(['getText'])
  },
  methods: {
    ...mapActions(['setText'])
  }
}
</script>

预览效果,能看到默认值和可以修改值就证明 vuex OK了

ES6+ 新特性兼容

安装依赖

npm i @babel/core@7.4.4 @babel/preset-env babel-loader core-js@3
  • @babel/core babel babel 核心包
  • @babel/preset-env 集成 bebal 一些可选方案,可以通过修改特定的参数来使用不同预设
  • babel-loader ES6+ES5
  • core-js 让不支持 ES6+ API 的浏览器支持新API

配置 webpack.config.js

		{
				test: /\.js$/,
				exclude: /node_modules/,
				use: ["babel-loader"]
		},

配置 .babelrc

根目录新建 .babelrc 文件,babel-loader会读取里面的配置

{
	"presets": [
		[
			"@babel/preset-env",
			{
				"useBuiltIns": "usage",
				"corejs": 3,
				"modules": false
			}
		]
	]
}

CSS 不同浏览器兼容性

安装依赖

npm i postcss-loader@4.3.0 autoprefixer@10.3.1 -D

配置 webpack.config.js

{
	test: /\.(css)$/,
	use: [
            "style-loader",
            "css-loader",
            "postcss-loader" //关键代码
	]
},
{
test: /\.(less)$/,
        use: [
            "style-loader",
            "css-loader",
            "less-loader",
            "postcss-loader",//关键代码
	]
},

配置 postcss.config.js

根目录新建 postcss.config.js ,这postcss-loader 的配置文件

module.exports = {
	plugins: {
		autoprefixer: {}
	}
}

配置 package.json

browserslist主要配置需要兼容的浏览器版本,和 package.jsondevDependencies 同级

 "browserslist": [
    "defaults",
    "not ie <= 8",
    "last 2 versions",
    "> 1%",
    "iOS >= 7",
    "Android >= 4.0"
  ],

结尾

其实这里 vue 最基本的开发环境已经搭建完成了,但不代表这个项目足够完善了。例如:ES6+ 语法转 ES5CSS不同浏览器兼容性;区分开发环境生产环境;还有一些优化的东西等等。篇幅原因就先写到这里。下一篇文章会续上

续集:

预告:

  • webpack 5 搭建 vue 开发环境

文章 demo 已发送到 github 请自行下载,】「demo 包括了第二篇的文章内容,推荐看完第二篇」 —— 「demo」

要是对你有帮助,麻烦点个赞再走~ 谢谢。