8.Webpack

作者: 璎珞纨澜 | 来源:发表于2019-07-14 17:38 被阅读0次

    在网页中会引用哪些常见的静态资源?

    • JS

    • .js .jsx .coffee .ts(TypeScript 类 C# 语言)

    • CSS

    • .css .less .sass .scss

    • Images

    • .jpg .png .gif .bmp .svg

    • 字体文件(Fonts)

    • .svg .ttf .eot .woff .woff2

    • 模板文件

    • .ejs .jade .vue【这是在webpack中定义组件的方式,推荐这么用】

    网页中引入的静态资源多了以后有什么问题?

    1. 网页加载速度慢, 因为 我们要发起很多的二次请求。
    2. 要处理错综复杂的依赖关系。

    如何解决上述两个问题?

    1. 合并、压缩、精灵图、图片的 Base64 编码。
    2. 可以使用之前学过的 requireJS、也可以使用 webpack 解决各个包之间复杂的依赖关系。

    基于上述两个问题的完美解决方案

    1. 使用 Gulp, 是基于 task 任务的。

    2. 使用 Webpack, 是基于整个项目进行构建的。

    • 借助于webpack这个前端自动化构建工具,可以完美实现资源的合并、打包、压缩、混淆等诸多功能。

    • 根据官网的图片介绍 webpack 打包的过程

    什么是 webpack?

    webpack 是基于 Node.js 开发出来的一个前端的项目构建工具。

    webpack官网

    webpack安装的两种方式

    1. 运行npm i webpack -g全局安装 webpack,这样就能在全局使用webpack 的命令。
    2. 在项目根目录中运行npm i webpack --save-dev安装到项目依赖中。

    webpack 最基本使用方式

    为了避免在页面中发起很多的二次请求导致的网页加载速度慢的问题,不推荐直接在网页中引用任何包和CSS文件。但当我们确实需要用到第三方包的时候,例如下面例子中的 Jquery ,因此我们思考是否可以像 Node 导入模块一样将 Jquery 包导入进来。于是我们将代码写成以下这样:

    index.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>Document</title>
      <script src="./main.js"></script>
    </head>
    <body>
      <ul>
        <li>这是第1个li</li>
        <li>这是第2个li</li>
        <li>这是第3个li</li>
        <li>这是第4个li</li>
        <li>这是第5个li</li>
        <li>这是第6个li</li>
        <li>这是第7个li</li>
        <li>这是第8个li</li>
        <li>这是第9个li</li>
        <li>这是第10个li</li>
      </ul>
    </body>
    </html>
    

    项目的入口文件 main.js

    // 导入jquery类库
    import $ from 'jquery'
    // 设置偶数行背景色,索引从0开始,0是偶数
    $(function () {
        $('li:odd').css('backgroundColor','lightblue')
        $('li:even').css('backgroundColor','red')
    })
    
    • import *** from *** 是 ES6 导入模块的方式
    • 由于 ES6 的代码太高级了,浏览器解析不了,所以会执行报错。
    • 所以我们考虑用 webpack 为我们打包处理程序,webpack默认会把这种高级的语法转换为低级的浏览器能识别的语法,从而让浏览器能够解析我们 main.js 写的代码。webpack 会将程序打包成一个 bundle。使用 webpack 打包指令 webpack .\src\main.js -o .\dist\bundle.js --mode development 生成 bundle.js 文件。
    • 我们在 index.html 中可以调用这个 bundle.js 文件,所以代码就变成下面这样:
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>Document</title>
      <script src="../dist/bundle.js"></script>
    </head>
    <body>
      <ul>
        <li>这是第1个li</li>
        <li>这是第2个li</li>
        <li>这是第3个li</li>
        <li>这是第4个li</li>
        <li>这是第5个li</li>
        <li>这是第6个li</li>
        <li>这是第7个li</li>
        <li>这是第8个li</li>
        <li>这是第9个li</li>
        <li>这是第10个li</li>
      </ul>
    </body>
    </html>
    

    webpack 最基本的配置文件的使用

    上面的方式需要手动指定入口文件,出口文件以及模式,且每次修改了代码之后都需要运行一遍,操作起来很麻烦。webpack 为我们提供了一种更为简便的方式,就是将运行 webpack 指令的参数配置到配置文件中。我们需要在项目根目录中建一个 webpack.config.js 的配置文件。

    将上面手动的指定的内容写到这个配置文件中,直接使用 webpack 打包指令 webpack 就可以打包。

    const path = require('path')
    
    module.exports = {
        entry: path.join(__dirname, './src/main.js'), // 入口
        output:{ // 输出文件配置
            path: path.join(__dirname, './dist'), // 指定打包好的文件输出目录
            filename: 'bundle.js' // 指定输出文件的名称
        },
        mode: 'development' // 指定 webpack 使用相应模式的内置优化
    }
    
    • 有没有发现 webpack 配置文件的语法和 Node.js 的语法一样?由于 webpack 是基于 Node.js 开发出来的,所以支持 webpack 语法。
    • 当我们在 控制台,直接输入 webpack 命令执行的时候,webpack 做了以下几步:
    1. 首先,webpack 发现,我们并没有通过命令的形式,给它指定入口和出口
    2. webpack 就会去 项目的 根目录中,查找一个叫做 webpack.config.js 的配置文件
    3. 当找到配置文件后,webpack 会去解析执行这个 配置文件,当解析执行完配置文件后,就得到了 配置文件中导出的配置对象
    4. 当 webpack 拿到配置对象后,就拿到了配置对象中,指定的入口和出口,然后进行打包构建。

    webpack-dev-server 的基本使用

    上面的方案虽然不用手动配置了,但是每次修改完代码还是需要再次执行 webpack 打包指令才能生效。我们能不能通过配置文件实现自动打包编译呢?我们想到在 node 里面我们曾经用过 nodemon 工具来实现自动编译。其实,在 webpack 中也有一个工具: webpack-dev-server ,可以实现自动打包编译的功能。

    1. 运行 npm i webpack-dev-server -D 把这个工具安装到项目的本地开发依赖
    2. 安装完毕后,这个工具的用法和 webpack 命令的用法完全一样。但是直接运行 webpack-dev-server ,会发现如下报错:
      运行报错
      原来,由于我们是在项目中本地安装的 webpack-dev-server , 所以无法把它当作脚本命令在powershell 终端中直接运行(只有那些安装到全局 -g 的工具,才能在终端中正常执行)。解决的方案就是我们需要在 package.json 文件中配置 scripts,所以我们加上如下配置:
      "scripts": {
        "dev": "webpack-dev-server"
      }
    

    注意: webpack-dev-server 这个工具,如果想要正常运行,要求必须在本地项目中安装 webpack,即执行命令 npm i webpack -D ,全局安装 webpack 也不管用。

    接下来就可以用 npm run dev 的指令启动程序了:

    npm run dev

    运行完后发现在 dist 目录并没有生成 bundle.js 文件,所以页面又变成了没有任何样式的样子:


    运行结果

    我们看运行完命令行的提示信息告诉我们项目被运行在了 http://localhost:8080/ ,而这个网页打开后是这样的,和我们项目根目录的结构一模一样:

    http://localhost:8080/

    webpack output is served from / webpack 输出文件正被托管于根路径。所以我们猜想我们的 bundle.js 文件肯定是在网站根路径下,打开后确实可以看到生成的 bundle.js 文件,那为什么在项目根路径中看不到这个文件呢?

    bundle.js
    1. 原来,webpack-dev-server 帮我们打包生成的 bundle.js 文件,并没有存放到实际的物理磁盘上,而是直接托管到了电脑的内存中,所以,我们在项目根目录中,根本找不到这个打包好的 bundle.js。

    那为什么要把打包好的 bundle.js 直接托管在内存中呢?
    是因为速度快,开发者可能经常性的操作保存编译,如果放到物理磁盘中,那么磁盘频繁读写速度即慢,也损耗资源,所以就放到内存中了。

    我们在 index.html 中需要调用内存中的这个 bundle.js 文件才能生效,所以我们的页面代码就应该变成下面这样:

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>Document</title>
      <script src="/bundle.js"></script>
    </head>
    <body>
      <ul>
        <li>这是第1个li</li>
        <li>这是第2个li</li>
        <li>这是第3个li</li>
        <li>这是第4个li</li>
        <li>这是第5个li</li>
        <li>这是第6个li</li>
        <li>这是第7个li</li>
        <li>这是第8个li</li>
        <li>这是第9个li</li>
        <li>这是第10个li</li>
      </ul>
    </body>
    </html>
    

    这时候打开网页的页面就可以看到已经生效了:


    image.png

    实现自动打开浏览器、热更新和配置浏览器的默认端口号

    增加这些配置可以使我们调试代码更为方便:

    • 要自动打开浏览器,可以在 package.json 中的 scripts 配置的 webpack-dev-server 中加上参数 --open
    • 要变更使用的端口可以加上参数 --port 3000
    • 要打开浏览器后就能直接看到网页,而不是像刚才的项目根目录一样的一大堆文件,可以加上参数 --contenBase src,将项目根目录指向 src 文件夹下,因为src文件夹下就有我们的 index.html ,所以我们打开浏览器就能直接看到网页了。
    • 实现热加载可以加上参数 --hot,加上后修改代码点击保存不会全部重新编译生成一个新的 bundle.js,而是将修改的那部分进行局部编译,并以增加 js 补丁的方式加载。
      "scripts": {
        "dev": "webpack-dev-server --open --port 3000 --contentBase src --hot"
      }
    
    • 上面讲到的 --open--port--contenBase--hot 参数,均可以放在 wbpack.config.js 的配置文件里面。
    const path = require('path')
    const webpack = require('webpack') //启用热更新(第 2 步)
    
    module.exports = {
        entry: path.join(__dirname, './src/main.js'), // 入口
        output:{ // 输出文件配置
            path: path.join(__dirname, './dist'), // 指定打包好的文件输出目录
            filename: 'bundle.js' // 指定输出文件的名称
        },
        mode: 'development', // 指定 webpack 使用相应模式的内置优化
        devServer: {
            open: true,// 自动打开浏览器
            port: 3000,// 设置启动时候的运行端口
            contentBase: 'src',// 指定托管的根目录 
            hot: true //启用热更新(第 1 步)
        },
        plugins: [ // 配置插件的节点
            new webpack.HotModuleReplacementPlugin() // new 一个热更新的模块对象, 启用热更新(第 3 步)
        ]
    }
    

    使用html-webpack-plugin插件配置启动页面

    由于使用 --contentBase 指令的过程比较繁琐,需要指定启动的目录,同时还需要修改 index.html 中 script 标签的 src 属性,所以推荐大家使用 html-webpack-plugin 插件配置启动页面。
    首先安装一下这个插件:
    npm i html-webpack-plugin -D
    然后在 wbpack.config.js 的配置文件中增加如下配置:

    const path = require('path')
    const webpack = require('webpack') //启用热更新(第 2 步)
    const htmlWebpackPlugin = require('html-webpack-plugin')
    
    module.exports = {
        entry: path.join(__dirname, './src/main.js'), // 入口
        output:{ // 输出文件配置
            path: path.join(__dirname, './dist'), // 指定打包好的文件输出目录
            filename: 'bundle.js' // 指定输出文件的名称
        },
        mode: 'development', // 指定 webpack 使用相应模式的内置优化
        devServer: {
            open: true,// 自动打开浏览器
            port: 3000,// 设置启动时候的运行端口
            contentBase: 'src',// 指定托管的根目录 
            hot: true //启用热更新(第 1 步)
        },
        plugins: [ // 配置插件的节点
            new webpack.HotModuleReplacementPlugin(), // new 一个热更新的模块对象, 启用热更新(第 3 步)
            new htmlWebpackPlugin({
                template: path.join(__dirname,'./src/index.html'),// 指定模板页面,从而根据指定页面路径生成内存中的页面
                filename: 'index.html' //指定生成页面的名称
            })
        ]
    }
    

    如此使用后可以将 index.html 中引用 bundle.js 的代码给去掉,而该插件还是能够将 bundle.js 引用的代码自动加上:


    内存中的页面

    在内存中生成 html 页面的插件:html-webpack-plugin
    作用:
    (1)自动在内存中根据 index 模板页面生成内存里的首页
    (2)自动把打包好的 bundle.js 追加到页面中去

    配置处理 css 样式表的第三方 loader

    我们可能想要为我们的界面导入一些自己写的样式,例如在刚刚的例子中我们要把无序列表前自带的点样式去掉。我们写了个这样的 css 样式。

    li{
        list-style: none
    }
    

    然后想要使用这个css文件,最常用的方法就是像我们之前那样在网页中像这样直接引用这个 css:<link rel="stylesheet" href="./css/index.css">
    但是记得我们开篇的时候说过为了避免在页面中发起很多的二次请求导致的网页加载速度慢的问题,不推荐直接在网页中引用任何包和CSS文件。所以我们在 main.js 中使用 import 语法导入这个 css 文件。
    main.js :

    // 项目的入口文件
    import $ from 'jquery'
    import './css/index.css'
    
    $(function () {
        $('li:odd').css('backgroundColor','lightblue')
        $('li:even').css('backgroundColor','red')
    })
    

    保存后发现编译报错了:


    编译报错

    我们发现原来 webpack 默认只能打包 JS 类型的文件,无法处理其它的非 JS 类型的文件。如果要处理非 JS 类型的文件,我们需要手动安装一些合适的第三方 loader 加载器。

    1. 如果想要打包处理 css 文件,需要安装 css-loader,安装执行:
      npm i style-loader css-loader -D
    2. 在 webpack.config.js 配置文件中新增一个 module 配置节点,这个节点用于配置所有第三方模块加载器,它是一个对象,在这个 module 对象身上,有个 rules 属性,这个属性是数组,存放了所有第三方文件的匹配和处理规则。
        module: { 
            rules: [
                { test: /\.css$/, use: ['style-loader', 'css-loader'] }
            ]
        }
    

    执行上面两步之后就可以将 css 文件中的样式应用到我们的页面中了。接下来我们给我们的页面导入 less、scss 文件,这和 css 文件导入的操作方法差不多,都是要安装对应的 loader 。
    处理 less 类型文件的配置

    1. 安装 less 和 less-loader 第三方包:npm i less less-loader -D
    2. 在 webpack.config.js 配置文件配置 module 节点 rules 属性:
      { test: /\.less$/, use: ['style-loader', 'css-loader', 'less-loader'] }

    处理 scss 类型文件的配置

    1. 安装 node-sass 和 sass-loader 第三方包:npm i node-sass sass-loader -D
    2. 在 webpack.config.js 配置文件配置 module 节点 rules 属性:
      { test: /\.scss$/, use: ['style-loader', 'css-loader', 'sass-loader'] }

    我们来总结一下 webpack 处理第三方文件的过程:

    1. 发现这个要处理的文件不是JS文件,然后就去 配置文件中,查找有没有对应的第三方 loader 规则
    2. 如果能找到对应的规则, 就会调用 对应的 loader 处理 这种文件类型;
    3. 在调用loader 的时候,是从后往前调用的,后面的 loader 处理完再将结果给前面的 loader 进行处理;
    4. 当最后的一个 loader 调用完毕,会把 处理的结果,直接交给 webpack 进行打包合并,最终输出到 bundle.js 中去

    webpack 中 url-loader 的使用

    默认情况下,webpack 无法处理 css 文件中的 url 地址,不管是图片还是字体库,只要是 url 地址都处理不了。需要使用 url-loader 才能处理。

    1. 安装 url-loader 和 file-loader 插件:npm i url-loader file-loader -D
    2. 在 webpack.config.js 配置文件配置 module 节点 rules 属性:
      { test: /\.(jpg|png|gif|bmp|jpeg)$/, use: 'url-loader' }

    使用 url-loader 处理图片

    当我们配置了url-loader,如果我们像下面这样在网页中插入图片:

    .box{
         width: 220px;
         height: 120px;
         background: url('../images/生小孩.jpg');
         background-size: cover;
    }
    <div class="box"></div>
    

    webpack 会自动将图片转码为 base64 格式的,并嵌入到网页中,这样能够减少图片的二次请求。因此我们看到我们网页中的图片就是想这样的base64 编码嵌入网页的。

    base64 编码格式图片
    但是有时候我们并不希望把所有图片都转码了,例如当图片比较大的时候。实际上小图片转为 base64 ,而大图片不转为 base64 这样比较合理。我们可以通过给 url-loader 传参数设置。修改 webpack.config.js 配置文件配置的 rules 如下:
    { test: /\.(jpg|png|gif|bmp|jpeg)$/, use: 'url-loader?limit=7630' }
    limit 给定的值是图片的大小,单位是 byte。如果我们引用了图片,当图片大小 <= limit 时,则会被转为 base64 格式的字符串,当图片 > limit 时,则不会被转码,所以直接请求 url 地址。
    图片 url 地址
    而为了防止图片的重名,请求的 url 地址会默认变更为图片文件的哈希值,哈希值是32位的,我们也可以自己设置 url 地址的格式,传参的时候设置 name 参数即可,例如我们这样设置就是取八位哈希值与图片文件的名称作为 url 地址名:
    { test: /\.(jpg|png|gif|bmp|jpeg)$/, use: 'url-loader?limit=7630&name=[hash:8]-[name].[ext]' }
    修改图片 url 格式

    使用 url-loader 处理字体文件

    bootstrap 中提供了很多的字体图标(https://v3.bootcss.com/components/),如果我们向应用到我们的网页中要怎么用呢,例如我们想要导入下面这个好看的爱心。

    bootstrap 字体图标
    首先在main.js 中导入 bootstrap:import 'bootstrap/dist/css/bootstrap.css'
    然后在配置 url-loader 处理字体文件:
    { test: /\.(ttf|eot|svg|woff|woff2)$/, use: 'url-loader'}
    然后就可以在我们的 index.html中插入这个爱心了:
    <span class="glyphicon glyphicon-heart" aria-hidden="true"></span>

    webpack 中 babel 的配置

    babel 官网:https://www.babeljs.cn/
    如果我们想在 main.js 中写下面这些代码:

    class Person {
        static info = { name: 'zs', age: 20}
    }
    console.log(Person.info)
    

    我们发现 webpack 无法识别这些较为高级的语法,于是编译报错了:


    编译报错

    原来,在 webpack 中只能处理一部分 ES6 的新语法,一些更高级的 ES6 语法或者 ES7 语法,webpack 是处理不了的;这时候,就需要借助第三方的 loader,来帮助 webpack 处理这些高级的语法,当第三方 loader 把高级语法转为低级语法之后,会把结果交给 webpack 去打包到 bundle.js。

    通过 babel,可以帮我们将高级的语法转换为低级的语法:

    1. 在webpack 中可以运行如下两套命令,安装两套包去安装babel相关的loader功能:
      1.1 第一套包:npm i babel-core babel-loader babel-plugin-transform-runtime -D
      这套包是 babel 的转换器。
      1.2 第二套包:npm i babel-preset-env babel-preset-stage-0 -D
      这套包是 babel 语法的对应关系。

    2. 修改 webpack.config.js 配置文件,在 module 节点下的 rules 数组中,添加一个新的匹配规则:
      2.1 { test: /\.js$/, use: 'babel-loader', exclude: /node_modules/ }
      2.2 注意:在配置 babel 的 loader 规则的时候,必须把 node_modules 目录,通过 exclude 选项排除。

      • 因为如果不排除 node_modules,则 babel 会把 node_modules 中所有的第三方 JS 文件都打包编译,会非常消耗 CPU,同时打包速度非常慢。
      • 而且就算 babel 把所有的 node_modules 中的 JS 转换完毕了,项目也无法正常运行。
    3. 在项目的根目录中新建一个叫做 .babelrc 的 babel 配置文件,这个配置文件属于 JSON 格式,所以在写 .babelrc 配置的时候,必须符合 JSON 语法规范,不能写注释,字符串必须使用双引号。
      在 babelrc 中写如下的配置:(其中 presets 是语法的意思)

    {
        "presets": ["env","stage-0"],
        "plugins": ["transform-runtime"]
    }
    

    上面三步配置完成后,本来已经大功告成了,

    但是我们执行 npm run dev后,却发现我们踩了好几个坑:
    坑一、 报错提示我们没找到@babel/core

    编译报错
    需要我们把babel-core卸载掉,重新安装@babel/core:
    npm un babel-core
    npm i @babel/core -D
    

    坑二、修改后再次运行发现又报错了,这次提示插件和语法不允许导出对象。

    编译报错
    https://babeljs.io/blog/2018/07/27/removing-babels-stage-presets

    需要我们将 babel-preset-* 卸载,重新安装 @babel/preset-*,另外,stage-*已弃用,所以同时修改 .babelrc 文件中的 presets。

    npm un babel-preset-env babel-preset-stage-0
    npm i @babel/preset-env -D
    

    坑三、再次运行报错,报错提示我们添加 @babel/plugin-proposal-class-properties

    image.png
    那我们安装一下 @babel/plugin-proposal-class-properties,并然后修改.babelrc文件中的 plugins。
    npm i @babel/plugin-proposal-class-properties -D
    

    坑四、提示 this.setDynamic 不是一个方法

    image.png
    这次是插件了,把 babel-plugin-transform-runtime 卸载,重新安装@babel/plugin-transform-runtime。然后修改.babelrc文件。
    npm un babel-plugin-transform-runtime
    npm i @babel/plugin-transform-runtime -D
    

    坑五 提示我们找不到 @babel/runtime 相关的文件夹

    编译报错
    那我们还需要安装@babel/runtime。
    npm i @babel/runtime -D
    

    安装完后终于没有报错,且控制台输出了我们想要的结果:


    编译运行

    综上排查后发现原来 babel 已经升级到7.X了,所以
    我们改一下我们的三大步配置步骤:

    1. 在webpack 中可以运行如下两套命令,安装两套包去安装babel相关的loader功能:
      1.1 第一套包:npm i babel-loader @babel/core @babel/runtime -D @babel/plugin-transform-runtime
      1.2 第二套包:npm i @babel/plugin-proposal-class-properties @babel/preset-env -D

    2. 修改 webpack.config.js 配置文件,在 module 节点下的 rules 数组中,添加一个新的匹配规则:{ test: /\.js$/, use: 'babel-loader', exclude: /node_modules/ }

    3. 在项目的根目录中新建一个叫做 .babelrc 的 babel 配置文件。
      在 .babelrc 文件中写如下的配置:

    {
        "presets": ["@babel/preset-env"],
        "plugins": ["@babel/plugin-proposal-class-properties","@babel/plugin-transform-runtime"]
    }
    

    webpack 中导入 vue

    以往我们导入 vue 都是直接用 script 标签的方式直接引用 vue 。我们能不能也用 import 导入 vue 呢?
    我们在项目的入口文件 main.js 中 import vue :

    // 项目的入口文件
    import Vue from 'vue'
    
    var vm = new Vue({
        el: '#app',
        data: {
            msg: '123'
        }
    })
    

    index.html 中用一个插值表达式试验一下:

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>Document</title>
    </head>
    <body>
      <div id="app"></div>
      <p>{{ msg }}</p>
    </body>
    </html>
    

    发现运行后网页报错提示正在用 runtime-only 编译。


    运行报错

    究竟是哪里出了问题呢?我们来回顾一下包的查找规则:

    1. 找项目根目录中有没有 node_modules 的文件夹
    2. 在 node_modules 中根据包名,找对应的 vue 文件夹
    3. 在 vue 文件夹中找一个叫做 package.json 的包配置文件
    4. 在 package.json 文件中,查找一个 main 属性【main 属性指定了这个包在被加载的时候的入口文件】

    我们发现在 vue 包的 package.json 文件中 main 属性指定的是 dist/vue.runtime.common.js 这个 js 文件

    vue 中 package.json 文件 main 属性
    原来以往我们指定的都是 dis/vue.js 这个文件,而 main.js 中使用 import Vue from 'vue' 导入的vue.runtime.common.js 中的 Vue 构造函数,功能不完善。
    vue 包 dist 目录

    解决的方法有以下几种:
    (1)import Vue from '../node_modules/vue/dist/vue.js'
    (2)在Vue包的package.json文件中main属性指定的入口文件修改为【"main": "dist/vue.js",】
    (3)在项目的 webpack.config.js 中添加 resolve 属性

    resolve: {
        alias: { // 修改 vue 被导入时候的包的路径
          "vue$": "vue/dist/vue.js"
        }
    }
    

    webpack 通过 render 函数渲染组件到容器中

    在 vue 中我们一定会用到组件的引用,当我们引用组件文件 .vue 文件的时候 webpack 可以解析么?答案是不可以。同理也是需要第三方 loader 来帮助 webpack 解析 .vue 文件的。那么我们来看看如何配置解析组件文件的:
    首先我们来新建一个最简单的 .vue 组件文件

    <template>
        <div>
            <h1>这是登录组件</h1>
        </div>
    </template>
    <script>
    
    </script>
    <style>
    
    </style>
    

    我们在 main.js 中导入这个组件文件:

    import Vue from 'vue'
    import login from './login.vue'
    
    var vm = new Vue({
        el: '#app',
        data: {
            msg: '123'
        },
        components: {
            login
        }
    })
    

    并在 index.html 页面上应用这个组件:

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>Document</title>
    </head>
    <body>
      <div id="app">
        <p>{{ msg }}</p>
        <login></login>
      </div>
    </body>
    </html>
    

    我们知道我们必须 webpack 在执行 main.js 入口文件的时候要解析 import login from './login.vue' 这句代码的 .vue 文件,必须用 vue-loader 插件去解析。步骤如下:

    • 安装 vue-loader :npm i vue-loader vue-template-compiler -D
    • 在 webpack.config.js 配置文件中 module 节点的 rules 属性中增加这个解析规则:
      { test: /\.vue$/, use: 'vue-loader' }
      我们在 npm run dev 开启后发现编译报错提示我们 vue-loader 需要有对应的 plugin。
      编译报错

    原来,Vue-loader在15.*之后的版本都是 vue-loader的使用都是需要伴生 VueLoaderPlugin的。按照官网的解释,我们需要在 plugins 插件节点配置 VueLoaderPlugin。配置完成后,可以正常编译开启网页,但是网页控制台又有报错:

    控制台报错

    这个报错是不是很熟悉,和前面的一样。你可能会说了,这不还是 import Vue from 'vue' 引用的 js 包不完整导致的。用前面的三种方法就可以解决了。但今天我们有另外一种方法:
    在 webpack 中,如果想通过 vue,把一个组件放到页面中去展示,也可以通过 vm 实例的 render 函数来实现。

    import Vue from 'vue'
    import login from './login.vue'
    
    var vm = new Vue({
        el: '#app',
        data: {
            msg: '123'
        },
        render: function (createElements) {
            return createElements(login)
        }
    })
    

    再编译就控制台就没有这个报错了:


    运行结果

    我们来总结一下 webpack 中如何使用 vue :

    1. 安装 vue 的包:npm i vue
    2. 由于在 webpack 中,推荐使用 .vue 组件模板文件定义组件,所以,需要安装能解析这种文件的 loader。安装 loader :npm i vue-loader vue-template-compiler -D
    3. 配置解析 loader ,在 webpack.config.js 中增加如下配置:
    const VueLoaderPlugin = require('vue-loader/lib/plugin')
    
    module.exports = {
        plugins: [ // 配置插件的节点
            new VueLoaderPlugin()
        ],
        module: { // 这个节点用于配置所有第三方模块加载器
            rules: [ // 所有第三方模块的匹配规则
                { test: /\.vue$/, use: 'vue-loader' }// 处理 .vue 文件的 loader
            ]
        }
    }
    
    1. 定义一个 .vue 结尾的组件,其中,组件有三部分组成:template script style
    2. 使用 import login from './login.vue' 导入这个组件
    3. 创建 vm 的实例 var vm = new Vue({ el: '#app', render: c => c(login) })(简写)
    4. 在页面中创建一个 id 为 app 的 div 元素,作为我们 vm 实例要控制的区域。

    export default 和 export

    在 node 中,我们使用规范的形式进行导入导出模块:

    1. 使用 module.exportsexports 向外暴露成员
    2. 使用 var 名称 = require('模块标识符') 导入模块
      在 ES6 中,我们也有类似的这样一套规范:
    3. 使用 export defaultexport 向外暴露成员
    4. 使用 import 名称 from 文件路径 导入模块
      我们来具体介绍下这个规范:
    • export default 向外暴露的成员,可以使用任意的变量来接收
    • 在一个模块中,export default 只允许向外暴露一次
    • 在一个模块中,可以同时使用 export default 和 export 来向外暴露成员
    • 使用 export 向外暴露的成员,只能使用 { } 的形式来接收,这种形式叫做按需导出,使用 export 导出的成员,必须严格按照导出时候的名称来使用 { } 按需接收
    • export 可以向外暴露多个成员,同时,如果某些成员,我们在 import 的时候不需要,则可以不再 { } 中定义
    • 使用 export 导出的成员,如果想换个名称来接收,可以使用 as 来起别名

    main.js

    import info123,{ title as title123, content} from '../test.js'
    console.log(info123)
    console.log(title123 + ' --- ' + content)
    

    test.js

    var info = {
        name: 'zs',
        age: 20
    }
    export default info
    
    export var title = '小星星'
    export var content = '哈哈哈'
    
    运行结果

    结合 webpack 使用 vue-router

    基本使用

    1. 安装 vue-router:npm i vue-router
    2. 通过 Vue.use() 明确地安装路由功能
      官网:vue-router 安装

    下面我们通过一个案例简单的案例介绍如何使用:
    main.js

    // 项目的入口文件
    import Vue from 'vue'
    // 1. 导入 vue-router 包
    import VueRouter from 'vue-router'
    // 2. 手动安装 VueRouter
    Vue.use(VueRouter)
    
    import app from './app.vue'
    import account from './main/Account.vue'
    import gooldslist from './main/Goodslist.vue'
    
    // 3. 创建路由对象
    var router = new VueRouter({
        routes: [
            { path: '/account', component:account },
            { path: '/goodslist', component:gooldslist }
        ]
    })
    
    var vm = new Vue({
        el: '#app',
        render: c => c(app),
        router // 4. 将路由对象挂载到 vm 上
    })
    

    App.vue

    <template>
        <div>
            <h1>这是 App 组件</h1>  
            <router-link to="/account">Account</router-link>
            <router-link to="/goodslist">Goodslist</router-link>
            <router-view></router-view>
        </div>
    </template>
    <script>
    </script>
    <style>
    </style>
    
    运行结果
    • App 组件是通过 VM 实例的 render 函数渲染出来的,render 函数会把 el 指定的容器中所有的内容都清空并覆盖,所以我们不把路由的 router-view 和 router-link 直接写到 el 所控制的元素中,而是写到 App 组件文件中。

    • 用 router-link 创建了两个链接,当点击链接修改 url 地址,url 地址修改后会被路由监听到。路由监听到后从上到下匹配 routes 规则,当匹配到 /account ,就展示 account 组件,当匹配到 /goodslist ,就展示 goodslist 组件。

    结合 webpack 实现 children 子路由

    如果我们想在 account 组件上挂载子路由,从而访问子组件,可以像这样:

    // 项目的入口文件
    import Vue from 'vue'
    // 1. 导入 vue-router 包
    import VueRouter from 'vue-router'
    // 2. 手动安装 VueRouter
    Vue.use(VueRouter)
    
    import app from './app.vue'
    import account from './main/Account.vue'
    import gooldslist from './main/Goodslist.vue'
    import login from './subcom/login.vue'
    import register from './subcom/register.vue'
    
    // 3. 创建路由对象
    var router = new VueRouter({
        routes: [
            { 
                path: '/account', 
                component:account,
                children: [
                    { path: 'login', component: login},
                    { path: 'register', component: register}
                ]
             },
            { path: '/goodslist', component:gooldslist }
        ]
    })
    
    var vm = new Vue({
        el: '#app',
        render: c => c(app),
        router // 4. 将路由对象挂载到 vm 上
    })
    

    Account.vue

    <template>
        <div>
            <h1>这是 Account 组件</h1>
            <router-link to="/account/login">登录</router-link>
            <router-link to="/account/register">注册</router-link>
            <router-view></router-view>
        </div>
    </template>
    <script>
    </script>
    <style lang="scss" scoped>
        body {
            div{
                font-style: italic;
            }
        }
    </style>
    

    login.vue

    <template>
        <div>
            <h1>这是 Account 的登录子组件</h1>
        </div>
    </template>
    <script>
    </script>
    <style scoped>
        div{
            color: red;
        }
    </style>
    
    运行结果
    • 普通的 style 标签只支持普通的样式,如果想要启用 scss 或 less,需要 style 元素,设置 lang 属性。
    • 如果我们的 style 标签不加上 scoped 属性,那么这个样式将是全局的。加上 scope 属性,那么样式只在当前组件生效,也就解决了样式的作用域问题。只要我们的 style 标签,是在 .vue 组件中定义的,那么推荐都为 style 开启 scoped 属性。
    • scoped 属性是如何解决样式作用域的问题的呢?
      下图我们可以看到在带有 scoped 属性的 DOM 元素,上面都带了该样式的属性选择器。由此可见,样式的 scoped 是通过 CSS 的属性选择器实现的。


      样式的属性选择器

    抽离路由模块

    当我们的项目中设计的路由越来越多,那么 main.js 的代码路由相关的代码也越来越多。一般开发中我们会将路由模块抽离出 main.js 中。将路由部分的代码放在 router.js 中,我们的 main.js 去导入 router.js,从而是的代码更为简洁易懂。我们尝试着把上面的小案例抽离路由模块:
    main.js

    // 项目的入口文件
    import Vue from 'vue'
    // 1. 导入 vue-router 包
    import VueRouter from 'vue-router'
    // 2. 手动安装 VueRouter
    Vue.use(VueRouter)
    
    import app from './app.vue'
    // 导入自定义路由模块
    import router from './router.js'
    
    var vm = new Vue({
        el: '#app',
        render: c => c(app),
        router // 4. 将路由对象挂载到 vm 上
    })
    

    router.js

    import VueRouter from 'vue-router'
    
    import account from './main/Account.vue'
    import gooldslist from './main/Goodslist.vue'
    import login from './subcom/login.vue'
    import register from './subcom/register.vue'
    
    // 3. 创建路由对象
    var router = new VueRouter({
        routes: [
            { 
                path: '/account', 
                component:account,
                children: [
                    { path: 'login', component: login},
                    { path: 'register', component: register}
                ]
             },
            { path: '/goodslist', component:gooldslist }
        ]
    })
    
    // 把路由对象暴露出去
    export default router
    

    相关文章

      网友评论

          本文标题:8.Webpack

          本文链接:https://www.haomeiwen.com/subject/jpmkqctx.html