webpack配置0~1 基础篇

扯淡的话

  • 扯淡1
    我同事问我为什么要用webpack,我自己也在思考为什么。上网也看了很多前辈的见解,也自己思考和比较后。总结下来就和我们用智能手机一样,习惯后,再去拿个普通手机怎么都是不方便的。这一个工具结合其loaders和plugins再配合上其他工具,就像是一个超级强大的智能手机。如果你还在用着老式的手机,可能这强大到你无从下手的东西让你开始有些抗拒,不过一旦你开始用它,你将会爱不释手。

  • 扯淡2
    我一直是把webpack当作一个工具,再加上vue-cli的webpack配置很完善,所以最开始我是拒绝好好学习一下的。不过看着vue-cli中把webpack配置的那么井井有条,又看了看自己的需要不断补全的webpack配置文件。我又忍不住摩拳擦掌想去尝试一下。

开始的话

为了方便自己知识的梳理和更加深入的理解一个有模有样的webpack配置的构成。我决定从0开始。所以有可能这篇文章还是在编写中,但会一直更新到我认为这份webpack配置已经能够面向生产使用了。

还有就是这份webpack配置是围绕vue项目来展开。而且,本着学新不学旧的原则,我使用的是webpack4版本。我总结的东西可能只是方便我自己理解的,当然,我说到的所有配置在webpack文档中,你基本都可以找到。

从0开始--基础部分

第一个需求

我期望可以在我的html中仅仅引入一个js文件而不是像很多比较老的项目一样引入长长的一排js

先针对这个问题。简单的来实现一个由webpack构建的小应用。

在此之前,我假设你已经知道了一些webpack的知识。首先要进行项目依赖安装。webpack4还需要安装webpack-cli。

分析项目

这个小型的项目对于webpack来说,只需要一个输入和输出就好了。
项目结构:

basewebpack.png
  • dist为打包好后的bundle.js存放目录(bundle也就是捆绑~的意思,就是把所有的模块都捆绑到这一个模块中)
  • src 存放源码的目录 index.js 为入口文件 依赖于document.js(超级简化版jq) 和 alert.js(超级简化版jq插件)。这是不是很符合以前使用jq开发的模式,现在我们利用webpack让我们只需要关注index.js即可
  • 其他文件。。。

webpack配置代码,只有输入输出:

const path = require('path') // 使用path是为了
module.exports = {
  entry: path.resolve(__dirname, 'src/index.js'), // 
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
}

运行 webpack --config webpack.conf.js在dist文件中得到bundle.js 并用index.html引用它即可

为了方便,配置build命令: webpack--mode production --config webpack.conf.js 关于mode,后面会说到

第二个需求

我看着打包后的bundle.js的代码

!function(e) {
  // 此处省略n行webpack构建的模块化代码
}([function (e, t, n) {
  "use strict";
  n.r(t);
// 简化版的jq
  class r {
    constructor() {}
    static getEle(e) {
      return document.querySelector(`${e}`)
    }
    static getEles(e) {
      return document.querySelectorAll(`${e}`)
    }
  }
// index代码
  r.getEle("#btn").addEventListener("click", e => {
// 简化版jq插件代码
    new class {
      constructor(e) {
        const t = document.createElement("div");
        t.innerHTML = `\n      <div style="position: absolute; width: 100vw; height: 100vh; background: rgba(0,0,0,.5);">\n        <div style="background: #fff; width: 10vw; height: 100px; line-height: 100px; margin: 40px auto; text-align: center;">\n          ${e}\n        </div>\n      </div>\n    `, r.getEle("body").appendChild(t)
      }
    }("哈哈哈")
  })
}])

我感到一丝的不安,这些花里胡哨的ES6代码再chrome上固然是没问题的,但我还是希望其可以转化为ES5代码,这样更为通用一些,同时,一些H5的新API(例如fetch)可以注入polyfill解决兼容性问题。这是第二个需求。

现在,只是依赖webpack的输入输出的基本能力已经解决不了这个问题了。需要引入webpack最强大的一个工具: loader。

我理解的webpack中的loader像一条流水线,不同的产品(代码)会交给不同的流水线(loader)处理。这里我们使用Babel和babel-loader来解决这一问题。

babel小插曲1

简介: babel是一个JavaScript编译器,可将ES6代码转换为ES5代码。
简单使用: 通过配置.babelrc配置文件(JSON格式)。 关于.babelrc配置详解会在后面补充,这里暂时不需要掌握这么多
安装babel依赖:这里需要安装三个babel的依赖:

npm i --save-dev babel-core babel-loader babel-preset-env // babel-preset-env可以根据支持的环境来自动决定适合的Babel插件

// 最简单的.babelrc配置:
{
  "presets": [ // presets作为一个数组,用来告诉babel需要对哪些语音新特性提供支持
    "env"
  ]
}

修改webpack配置文件
const path = require('path')
module.exports = {
  entry: path.resolve(__dirname, 'src/index.js'),
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.js$/, // 提供正则来匹配.js结尾的文件
        use: ['babel-loader'] // 匹配到这一类文件后,将其交给哪些loader去处理,这里我们先配置为最简单的模式
      }
    ]
  }
}

ok,这样在通过babel处理后的ES6代码就转换为了ES5的通用代码(转换后的代码不做展示了)

第三个需求

我想我要开始使用框架了,人家都用vue,我也要尝试下。

一开始也像前面那样引入js文件一样引入vue

import {alert} from './libs/alert.js'
import Vue from 'vue/dist/vue.js' // 关于这里有个问题,大家可以将/dist/vue.js去掉看一下会有什么问题
const vm = new Vue({
  el: '#app',
  data: {

  },
  methods: {
    showModel(str) {
      new alert(`你好${str}`)
    }
  }
})

但是这样终究没办法让开发者更好地利用vue组件化的威力。何不像vue-cli的项目那样,使用.vue文件来开发项目呢?

vue-loader

上面介绍过一个处理js文件的loader,那么对于.vue文件,官方也有一个对应的vue-loader来处理它

npm i vue-loader css-loader vue-template-compiler --save-dev  // 由于vue-loader依赖于css-loader vue-template-compiler 用于将vue-loader提取的html模板编译为对应的JavaScript代码

现在自己定义一个通过.vue构成的项目

/*
  index.js 代码
*/
import Vue from 'vue/dist/vue.esm'
import App from './App.vue'

const vm = new Vue({
  el: '#app',
  components: { App },
  template: '<App/>'
})
/*
webpack 配置
*/
const path = require('path')
module.exports = {
  entry: path.resolve(__dirname, 'src/index.js'),
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.js$/, // 提供正则来匹配.js结尾的文件
        // use: ['babel-loader'] // 匹配到这一类文件后,将其交给哪些loader去处理,这里我们先配置为最简单的模式
        loader: 'babel-loader'
      },
      {
        test: /\.vue$/, // 匹配.vue结尾的文件
        loader: 'vue-loader'
      }
    ]
  }
}
踩坑1

这时候,出问题了。
vue-loader was used without the corresponding plugin. Make sure to include VueLoaderPlugin in your webpack config. 在webpack4中编译打包vue文件的时候需要引入VueLoaderPlugin

而这里,也要引入webpack的第二个利器,plugins。我理解的plugins就像是工厂中的一些处理工具,是辅助流水线(loader)来更好的处理代码的存在。

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

module.exports = {
  entry: path.resolve(__dirname, 'src/index.js'),
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.js$/, // 提供正则来匹配.js结尾的文件
        // use: ['babel-loader'] // 匹配到这一类文件后,将其交给哪些loader去处理,这里我们先配置为最简单的模式
        loader: 'babel-loader'
      },
      {
        test: /\.vue$/, // 匹配.vue结尾的文件
        loader: 'vue-loader'
      }
    ]
  },
  plugins: [
    new VueLoaderPlugin()
  ]
}

这时,运行这个webpack配置文件又出错了:


cssloader.png

对于.vue文件中的css代码,需要css-loader来处理。在上面代码中补上这样一段代码:

      {
        test: /\.css$/, 
        loader: 'css-loader'
      }

打包后~~ 为什么样式没有生效啊。这里需要再加上一个style-loader

      {
        test: /\.css$/, 
        loader: 'style-loader!css-loader'
      }

一般来说需要引入css-loader和style-loader,其中css-loader用于解析,而style-loader则将解析后的样式嵌入js代码。

到这,很简单的一个vue组件已经可以展示出来了。


demo1.png

第四个需求

用上vue后,我迫不及待的写了几行代码,却发现每次要验证代码的效果都需要执行一次webpack的配置文件。这让我很苦恼。我希望我每次改完我的代码都可以在页面中直接体现出来。

webpack的开发者服务器亮相了:npm i webpack-dev-server --save-dev devServer文档说明

在写配置之前,使用devServer的一些基本东西我想了解到:

  • 1、如何启动开发服务器
  • 2、如何配置开发服务器的例如host和port等配置项
  • 3、我们启动的服务器是以哪个html文件为模板显示在浏览器中的。如何配置这个模板

使用开发服务器来启动项目,那自然命令和之前的不同,那配置一个dev命令来表示以devServer的模式启动项目: webpack-dev-server --mode development --config webpack.conf.js

现在可以启动服务器后,可以看到通过浏览器自动打开了一个页面:


server1.png

webpack提供一个devServer属性,可以在其中方便的配置devServer的各种属性,像页面中体现的localhost(host属性)和8080(port)都是可以自定义来配置的,除此之外,也可在前端使用devServer来进行代理配置等等,属性很多,功能很强大,请参考devServer文档说明,这里不一一赘述。

最后一个疑惑🤔?如何去配置模板页面?devServer的historyApiFallback属性可配置在URL命中不同规则时返回不同的页面,当然也可以用最简单的historyApiFallback: true来规定无论什么URL,都返回index.html。我把之前的index.html清理一下作为devServer的模板页面:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
  <style>
    * {
      margin: 0;
      padding: 0;
    }
  </style>
</head>
<body>
  <div id="app">
    <!-- <button id="btn" @click="showModel('哈哈')">请点击</button> -->
  </div>
  <!-- <script src="dist/bundle.js"></script> -->
</body>
</html>

除了创建模板页面外,还需要html-webpack-plugin插件。因为我们的模板页面是没有引入我们编译后的文件,所以需要这个插件来根据模板html创建一个引入了编译后的js,css文件的html文件并将其展示到浏览器中。这个插件当然不仅仅是这一点作用,这里我们只需要明白它的这一用处即可。

修改webpack配置文件:

const path = require('path')
const VueLoaderPlugin = require('vue-loader/lib/plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  entry: path.resolve(__dirname, 'src/index.js'),
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.js$/, // 提供正则来匹配.js结尾的文件
        // use: ['babel-loader'] // 匹配到这一类文件后,将其交给哪些loader去处理,这里我们先配置为最简单的模式
        loader: 'babel-loader',
        exclude: '/node_modules/'
      },
      {
        test: /\.vue$/, // 匹配.vue结尾的文件
        loader: 'vue-loader',
        exclude: '/node_modules/'
      },
      {
        test: /\.css$/, 
        loader: 'style-loader!css-loader',
        exclude: '/node_modules/'
      }
    ]
  },
  plugins: [
    new VueLoaderPlugin(),
    new HtmlWebpackPlugin({
      template: 'index.html' // 告诉HtmlWebpackPlugin插件使用哪个模板
    })
  ],
  devServer: {
    historyApiFallback: true,
  }
}

执行npm run dev:

server1.png

修改一下代码:

server2.png

这将会大大提升开发效率

基础部分完成

到这里,基础部分的内容就完成了。现在的这个webpack配置是非常粗糙但是能完成基础的需求了。接下来我会结合Vue完成进阶部分的内容。

第二篇地址

最后github地址,求star

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