不使用 vue-cli 与 vue 模版,使用 Vue2.x + webpack4.x 从零开始一步步搭建项目框架

说明

这是我根据慕课网上的一个课程 Vue+Webpack打造todo应用 过程一步步搭下来的框架,去掉了业务相关的逻辑。
项目最终的效果包括了引入vue框架;使用CSS预处理器;使用babel;引用图片等静态资源;区分开发环境与生成环境,并做相应优化等。基本接近真正做项目时候的配置。
但是!! 毕竟是我个人根据练习课程搭的框架,跟真实工作可能有区别,请谨慎直接用于工作环境!!!

项目的最终成果看这里:https://gitee.com/Dandelion_/vue-webpack-scaffold
Tips:项目里面的 commit 对应文章的每一小节,所以大家善用 git checkout <commit> 命令可以很方便地切换到某个 commit 看当前版本的文件变化哦。当然直接看项目的 commit 列表也行啦。


0. 前言

首先不得不说 vue-cli + vue-webpack 模版真的很方便,vue init webpack my-vue-project 就搭好框架了,而且开发环境生产环境都有了。npm run dev 启动开发环境,npm run build 发布生产环境。几个命令全部搞定了。但是模版有时候可能不够灵活,或者我们想修改其中一些东西,面对这些需求的时候,理解怎么搭建起这个 vue + webpack 的环境还是很有用的。那最快捷的方式,当然是自己从头开始搭一遍啦。心动不如行动,走~~


1. 新建项目

因为不用 vue-cli 和 vue 模版,所以一开始我们就建个空文件夹。

mkdir my-vue-project
cd my-vue-project
npm init # 初始化项目。这时候会问你一些问题,比如项目名称、作者之类的,照着提示回答问题就好

2. 安装基本的 npm 包

首先肯定要安装 vuewebpack。然后 webpack 4.x已经把 cli 单独拎出来了,所以还要安装 webpack-cli;然后因为 webpack 本身其实直接能处理的只有 js 资源,是通过各种 loader 让其他资源可以被 webpack 打包处理的。那么现在我们要用 vue 写单文件组件(就是 .vue 文件),所以就还要安装 vue-loader

npm install vue vue-loader webpack webpack-cli --save-dev # --save-dev表示这些只是开发环境所需的依赖

3. 添加项目各种入口文件

入口文件都是很普遍的那种,比如根目录下的 index.htmlsrc 文件夹下的 main.jsapp.vue 等等,我就不一一解释了。

添加入口文件后的项目目录如下:

.
├── dist/
|   ├── ...
├── src/
|   ├── main.js
|   ├── app.vue
├── node_modules/
|   ├── ...
├── index.html
├── package.json

更详细的项目结构和具体文件内容可以看 这个commit

4. 添加 webpack 配置文件

在项目根目录下添加 webpack.config.js 文件。内容如下:

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

module.exports = {
    entry: path.join(__dirname, 'src/main.js'), // 项目总入口js文件
    // 输出文件
    output: {
        path: path.join(__dirname, 'dist'), // 所有的文件都输出到dist/目录下
        filename: 'bundle.js'
    },
    module: {
        rules: [{
                // 使用vue-loader解析.vue文件
                test: /\.vue$/,
                loader: 'vue-loader'
            },
            {
                test: /\.css$/,
                // 要加上style-loader才能正确解析.vue文件里的<style>标签内容
                use: ['style-loader', 'css-loader']
            }
        ]
    },
    plugins: [
        new VueLoaderPlugin() // 最新版的vue-loader需要配置插件
    ]
};

5. 添加构建脚本

package.json 文件的 scripts 属性里添加 build 脚本

   "scripts": {
     "test": "echo \"Error: no test specified\" && exit 1",
+    "build": "webpack --config webpack.config.js"
   },

因为我们添加了一些引用,比如 style-loadercss-loader 等,所以也要安装相应的包。

npm i style-loader css-loader vue-template-compiler --save-dev

安装好依赖后运行 npm run build 来构建项目。
构建成功后在项目根目录启动一个静态资源服务器,然后浏览就可以看到以下页面了。

1.jpg

6. 添加图片、CSS 预处理器等 loader

图片 loader 用的是 url-loader,它依赖于 file-loader,比 file-loader 多了一个可以比小于一定大小的图片直接转化成 base64 的形式插入到 html 页面,可以减少网络请求。

CSS 预处理器 我选的是 SASS

安装依赖:

npm i file-loader url-loader node-sass sass-loader --save-dev

修改 webpack.config.js

        module.exports = 
            {
                 test: /\.css$/,
                 // 要加上style-loader才能正确解析.vue文件里的<style>标签内容
                 use: ['style-loader', 'css-loader']
+            },
+            {
+                test: /\.scss$/,
+                use: [
+                    // 处理顺序是从sass-loader到style-loader
+                    'style-loader',
+                    'css-loader',
+                    'sass-loader'
+                ]
+            },
+            {
+                test: /\.(gif|jpg|jpeg|png|svg)$/i,
+                use: [{
+                    loader: 'url-loader',
+                    options: {
+                        // 当文件大小小于limit byte时会把图片转换为base64编码的dataurl,否则返回普通的图片
+                        limit: 8192,
+                        name: 'dist/assest/images/[name]-[hash:5].[ext]' // 图片文件名称加上内容哈希
+                    }
+                }]
             }
         ]
     },
     ...

测试一下:

添加 src/assets/styles/global.scss 文件

+.play {
+    background-image: url('../images/play.png');
+    background-repeat: no-repeat;
+    width: 64px;
+    height: 64px;
+    padding: 0;
+    border: 0;
+    background-color: transparent;
+    margin: 0;
+    cursor: pointer;
+}

src/main.js 引用 global.scss

 import Vue from 'vue'; // 从node_modules引入vue类库
 import App from './app.vue'; // ES6 语法,相当于 import { default as App } from './app.vue'。因为app.vue用过的是export default {...},所以可以这样写
  
+import './assets/styles/global.scss';
+
 new Vue({
     el: '#app',
     components: {

运行 npm run build,刷新页面,能看到如下效果:

2.jpg

button 的背景图是 1.97kb,小于 8192byte,可以看到图片已经被转换成 base64 的内容了。

7. 添加 postcss-loader + autoprefixer,自动添加 css 浏览器前缀

安装依赖:

npm i postcss-loader autoprefixer --save-dev

新增 postcss 配置文件 postcss.config.js

const autoprefixer = require('autoprefixer');

module.exports = {
    plugins: [
        autoprefixer({
            browsers: ['last 5 versions']
        })
    ]
};

修改 webpack.config.js

    ...
    module: {
        rules: [
            ...
            {
                test: /\.css$/,
                use: [
                    // 要加上style-loader才能正确解析.vue文件里的<style>标签内容
                    'style-loader',
                    {
                        loader: 'css-loader',
                        options: {
                            importLoaders: 1
                        }
                    },
                    'postcss-loader'
                ]
            },
            {
                test: /\.scss$/,
                use: [
                    // 处理顺序是从sass-loader到style-loader
                    'style-loader',
                    'css-loader',
                    {
                        loader: 'postcss-loader',
                        options: {
                            sourceMap: true
                        }
                    },
                    'sass-loader'
                ]
            },
            ...

测试一下:

修改 index.html

     <h1>一个用 vue + webpack 搭建的脚手架框架</h1>
     <div id="app"></div>
     <button class="play"></button>
+    <div class="flex">
+        <div class="flex-item"></div>
+        <div class="flex-item"></div>
+        <div class="flex-item"></div>
+    </div>
     <script src="/dist/bundle.js"></script>
 </body>

修改 global.scss


+.flex {
+    display: flex;
+    border: solid #000 2px;
+    width: 300px;
+    height: 100px;
+    .flex-item {
+        flex: 1;
+        background-color: #1296db;
+        border: solid #fff 1px;
+    }
 }

运行 npm run build,刷新页面,能看到如下效果:

3.jpg

可以看到 flex 已经加上了浏览器前缀了。

8. 添加 babel-loader,转译 es6 代码为 es5 代码

添加.babelrc文件

{
    "presets": [
        "env"
    ],
    "plugins": [
        "transform-vue-jsx"
    ]
}

安装依赖

npm i babel-loader@7 babel-core babel-preset-env --save-dev

修改 webpack.config.jsmodule.rules 再加一条:

{
    test: /\.js$/,
    exclude: /(node_modules|bower_components)/, // 不处理这两个文件夹里的内容
    loader: 'babel-loader'
}

测试一下:

修改 main.js,加上以下代码

// 测试babel-loader
class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }

    sayHello() {
        console.log(`Hello, my name is ${this.name}`);
    }
}

在浏览器查看输出,如下图:


4.jpg

如果注释掉 webpack.config.jsbabel-loader 这条规则,则编译之后如下:

5.jpg

可以看到 babel-loader 已经起作用了。

9. 添加 html-webpack-plugin,自动生成 index.html 的内容

添加 HtmlWebpackPlugin,在 dist 文件夹也生成 index.html 页面,而且会自动加上对项目入口 js 文件的引用,也就是说我们自己写的 index.html 可以不用再手动指定 js 文件了,它会把 webpack 配置里的 entry 当中指定的 js 都插入到生成的 index.html 中,而且当输出的文件添加上 hash 值时也可以自动跟踪。

安装依赖

npm i html-webpack-plugin --save-dev

修改 webpack.config.js

 const path = require('path');
 const VueLoaderPlugin = require('vue-loader/lib/plugin');
+const HtmlWebpackPlugin = require('html-webpack-plugin');
  
 module.exports = {
     entry: path.join(__dirname, 'src/main.js'), // 项目总入口js文件
     // 输出文件
     output: {
         path: path.join(__dirname, 'dist'),
-        filename: 'bundle.js'
+        filename: 'bundle-[hash].js' // 输出文件的名称加上hash值
     },
     module: {
         rules: [{
             ...
         ]
     },
     plugins: [
-        new VueLoaderPlugin() // 最新版的vue-loader需要配置插件
+        new VueLoaderPlugin(), // 最新版的vue-loader需要配置插件
+        new HtmlWebpackPlugin({
+            filename: 'index.html', // 生成的文件名称
+            template: 'index.html', // 指定用index.html做模版
+            inject: 'body' // 指定插入的<script>标签在body底部
+        })
     ]
 };

修改 index.html,把原来引用 bundle.js<script> 去掉。

然后我们 build 一下,看 dist 文件夹的输出。

6.jpg

可以看到 dist 文件夹下多了 index.html 文件,而且文件内容自动加上了编译出来的 bundle.js 文件的引用。

10. 添加 clean-webpack-plugin,每次 build 之前可以自动先清除输出文件夹

如果我们对输出的文件名称加上了哈希值的话,每次修改之后的哈希值都不一样,就是每次都生成了一个新的文件,那旧的那些其实就是多余的文件了(上面那张图的 dist 文件夹可以看出来)。因此我们可以用 clean-webpack-plugin 这个插件,在每次 build 之前先清除输出文件夹。

安装依赖

npm i clean-webpack-plugin --save-dev

修改 webpack.config.jsplugins 数组添加一项

 const path = require('path');
 const VueLoaderPlugin = require('vue-loader/lib/plugin');
 const HtmlWebpackPlugin = require('html-webpack-plugin');
+const CleanWebpackPlugin = require('clean-webpack-plugin');
  
 module.exports = {
     ...
     plugins: [
        new VueLoaderPlugin(), // 最新版的vue-loader需要配置插件
        new HtmlWebpackPlugin({
            filename: 'index.html', // 生成的文件名称
            template: 'index.html', // 指定用index.html做模版
            inject: 'body' // 指定插入的<script>标签在body底部
        }),
+        new CleanWebpackPlugin(['dist'])
    ]
 };

11. 添加 webpack-dev-server,配置更友好的开发环境

webpack-dev-server 可以在本地启动一个服务器,而且当有任何文件修改的时候会自动重新打包,并且刷新浏览器页面。此外 devServer 还有很多其他配置项,让我们可以更方便的开发。

安装依赖

npm i webpack-dev-server cross-env --save-dev

用上 cross-env 是因为我们接下来要修改 package.json 里的 scripts,而不同的平台写scripts 的方式不一样。

修改 package.json 里的 scripts

    ...
     "scripts": {
         "test": "echo \"Error: no test specified\" && exit 1",
-        "build": "webpack --config webpack.config.js"
+        "build": "cross-env NODE_ENV=production webpack --config webpack.config.js",
+        "dev": "cross-env NODE_ENV=development webpack-dev-server --config webpack.config.js"
     },
   ....

修改 webpack.config.js,这次改得比较多,可以看这里的 commit detail

配置好以后我们可以运行 npm run dev 命令启动一个服务器了

12. 配置生产环境 css 单独分离打包,方便浏览器缓存

安装依赖

npm i mini-css-extract-plugin --save-dev

修改 webpack.config.js,改动的地方看这个 commit detail

13. 单独打包类库文件

因为类库文件是不用像业务代码一样经常更新的,单独打包可以让它们在浏览器里缓存,提高加载速度。

修改 webpack.config.js,改动的地方看这个 commit detail


到这里基本的配置都完成了,一个基础的 vue 项目框架就搭好啦,接下来我们只需要专注于添加自己项目的业务逻辑页面和代码就好了。愉快地敲(xie)代(bug)码去吧~~

PS: 这个是目前为止用到的配置,其实真实生成环境要做的优化应该还有不少,比如图片压缩处理等等,大家要根据自己的项目需要来扩展哦。如果以后有用到其他的插件比较通用的应该会更新这篇文章。嗯,也就是不定期无规律看心情更新2333【逃。。】


本文首发于我的个人网站【https://dandelion-drq.github.io】,欢迎访问。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容