资讯专栏INFORMATION COLUMN

【30分钟】吃透webpack,也许这一篇就够了

KitorinZero / 777人阅读

摘要:否则可能会导致全局安装的版本与项目中的配置文件可能存在不匹配。是一个流行的插件,其作用是为中的属性添加浏览器特定的前缀。插件允许开发人员在当前的项目中使用将来版本中可能会加入的新特性。

本文webpack是在Mac平台下基于官方最新版本v3.10,对于webpack@v2会有小的差异,待全文完成后会补充webpack@v2与v3版本之间的差异
使用webpack前的准备 1、初始化一个前端项目

为了方便之后自己更好的使用这个webpack_starter,引入git的支持,一是可以把一些通用的东西放在主分支,二是可以把后面不同的配置支持可以通过branch或者tag的方式分门别类。

在github初始化一个webpack_starter项目,如下图所示,初始化.gitignore支持Node语言

#clone项目到本地
git clone https://github.com/mpandar/webpack_starter.git
cd webpack_starter

利用yarn初始化项目(当然同样可以使用npm,无太大差异,此处不再补充npm使用方法)

初始化过程按照提示完成即可,唯一注意的是entry point,这是webpack进行打包时的入口文件,默认是根目录下的index.js,不过通常情况下,我们的源码都是在src目录下,所以修改为src/index.js
> yarn init
yarn init v1.3.2
question name (webpack_starter): 
question version (1.0.0): 0.1.0
question description: a webpack start project
question entry point (index.js): src/index.js
question repository url (https://github.com/mpandar/webpack_starter.git): 
question author (mpandar ): 
question license (MIT): 
question private: 
success Saved package.json
✨  Done in 53.55s.
2、安装webpack
多带带安装与全局安装对于webpack的使用并无太大差异,但推荐即使全局安装以后,仍要在项目中进行多带带的安装,方便项目移植。否则可能会导致全局安装的webpack版本与项目中的配置文件可能存在不匹配。当然多带带安装后,使用一些npm或yarn命令,它们会优先使用本地安装的webpack
#全局安装
yarn global add webpack
#单项目使用
yarn add webpack
3、初始化项目目录
 |---
    |--dist    //存放webpack打包后相关文件
    |--src     //存放项目源码
       |--index.js 
    |--config  //项目相关的配置文件
       |--webpack.config.js   //webpack默认读取项目根目录下的webpack.config.js文件作为配置信息,为了规范化移入到config目录下
    |--package.json
了解webpack配置文件webpack.config.js

当然,即使没有配置文件,直接运行webpack命令,同样可以直接对js文件完成打包工作,这里是一篇分析webpack打包后的代码的文章:简要分析webpack打包后代码,其中用到的一个新命令npx,很简单,介绍点这里

#直接打包
npx webpack src/index.js dist/bundle.js

为了应对更灵活的使用场景,webpack支持配置文件,并且默认情况下,在项目根目录下,如果存在webpack.config.js文件,那么webpack会主动读取该文件作为配置内容,不过Demo下,为了更加符合我们的目录规范,我们将config文件移到了config目录下

//当配置文件内容为空时,运行该命令会提示`Configuration file found but no entry configured`
npx webpack src/index.js --config config/webpack.config.js

接下来,让我们看一下一个webpack配置文件,最简单只需要包含entry(定义入口文件)和output(定义打包输出文件)这两个部分:

const path = require("path");
const base = path.join(__dirname, "..")

module.exports = {
  entry: path.resolve(base, "src", "index.js"),
  output: {
    filename: "bundle.js",
    path: path.resolve(base, "dist")
  }
};
注:使用path模块只是为了代码清晰,你完全可以不用,直接用__dirname+"/../src"类似代码拼接
//这样就不需要在命令行定义输入输出文件啦
npx webpack --config config/webpack.config.js

除了entryoutput,webpack中最常见的就是moduleresolveplugins,大致结构如下:

module.exports = {
  entry: path.resolve(base, "src", "index.js"),
  output: {
    filename: "bundle.js",
    path: path.resolve(base, "dist")
  },
  devtool: "eval-source-map",
  devServer: {
    contentBase: path.resolve(base, "dist"),
    historyApiFallback: true,
    inline: true,
    proxy: {
      "/api": "http://localhost:8000"
    }
  },
  module: {
    rules: [
    ]
  },
  resolve: {
  },
  plugins: [
  ]
};

当然了解webpack最好的地方永远是官方文档,传送门,接下来,自然是在目前配置的基础上,增添更多令人兴奋的特性

为开发增加更多利器 生成Source Map,为调试助力

开发离不开调试,但经过编码后的代码并不利于调试,很找到出错的地方对应的你写的代码,而Source Maps就是来帮我们解决这个问题的。
而webpack支持Source Maps仅仅是增加一行 devtool 配置选项,具体配置选项可以看这里的官方文档,其中两个选项 eval-source-mapsource-map 是比较常用的选项,前一个选项推荐仅仅用在开发环境,而后一个通常在一些第三方库中,提供给开发者调试使用。当然对于任何上线项目,实际上都推荐使用*.min.js并不使用Source Map以加快网络加载。

module.exports = {
  entry: path.resolve(base, "src", "index.js"),
  output: {
    filename: "bundle.js",
    path: path.resolve(base, "dist")
  },
  devtool: "eval-source-map"
}
自动监控代码更新,自动编译,自动浏览器刷新

作为开发者,总不希望把时间浪费在执行命令和刷新页面上,webpack提供一个多带带的组件webpack-dev-server为我们提供一个基于nodejs的本地服务器、文件修改监控及编译以及浏览器自动刷新等特性。

首先是安装:

yarn add webpack-dev-server --dev

详细配置参数可查阅官方文档,常使用参数如下:

contentBase 指定项目根目录

historyApiFallback 主要应用在单页应用的开发场景,它依赖于HTML5 history API,如果设置为true,所有的跳转将指向index.html

inline 主要是解决了自动浏览器自动刷新问题

lazy 默认为false,若开启后,则webpack-dev-server不再监测文件变化自动编译,只有等到我们手动刷新浏览器的时候才会编译文件

proxy 可以有效的解决开发阶段的跨域问题,毕竟不是所有的前端项目,都有独立的nodejs作为中间件去请求服务,更多的还是把前端项目编译后与后端服务部署在一起。而开发阶段又相对独立,这时候就可以利用proxy将前端请求转发到后端测试服务器,不影响开发

module.exports = {
  entry: path.resolve(base, "src", "index.js"),
  output: {
    filename: "bundle.js",
    path: path.resolve(base, "dist")
  },
  devtool: "eval-source-map",
  devServer: {
    contentBase: path.resolve(base, "dist"),
    historyApiFallback: true,
    inline: true,
    proxy: {
      "/api": "http://localhost:8000"
    }
  }
}
小插曲,本来测试proxy的时候,自己利用php -S localhost:8000快速起了一个监听进程,但是访问前端的时候却提示转发去请求被拒绝,后来发现webpack-dev-server去转发请求的时候是把localhost转化为了地址,即127.0.0.1:8000,所以在php启动监听进程时候,需要使用php -S 127.0.0.1:8000或者php -S 0.0.0.0:8000监听所有网卡地址

webpack-dev-server的使用同webpack基本运行一样,只是webpack-dev-server是一个不会退出的进程,并自动监控文件变化等(Ctrl+C退出)

npx webpack-dev-server --config config/webpack.config.js
更方便的打包命令

使用npx webpack --config config/webpack.config.js进行打包实际上已经很方便了,但是当我们需要又有开发环境的配置,又有生产环境的配置,甚至还要为命令增加其他的环境变量的时候,这个命令简直是又臭又长,我们总不能每次都输入这个繁琐的命令,其实我们可以利用npm scripts,这里是一篇阮一峰大神对npm脚本的介绍

//package.json
{  
  "scripts": {
    "build": "webpack --config config/webpack.config.js",
    "dev": "webpack-dev-server --config config/webpack.config.js"
  }
}
npm run build
npm run dev
需要注意的是在配置build&dev脚本的时候,我们并没有用npx命令,实际上,在npm脚本中的命令,npm默认都是优先查找本项目下node_modules下是否存在对应的模块及命令,如果没找到,才会查找全局的命令
多文件入口

通常一个项目中,并不是只有一个js文件,而entry默认支持多文件入口,修改output跟随入口文件名字命名即可,简单做如下修改:

module.exports = {
  entry: {
    index: path.resolve(base, "src", "index.js"),
    main: path.resolve(base, "src", "main.js")
  },
  output: {
    filename: "js/[name].js",
    path: path.resolve(base, "dist")
  }
}

同时创建一个简单的main.js进行测试,再次编译会发现在dist/js目录下存在编译后的index.js和main.js文件

一切皆为Module

webpack令人兴奋的一个特性就是模块化,易于扩展其功能。webpack支持大量的模块导入方法,比如ES6(import)、CommonJS(require)、AMD等规范,并且加入了一些自由方法,具体的可以看官方说明文档。

用通俗的语言描述就是,webpack通过某个入口,去匹配各种模块导入规范,发现一个模块就根据对应配置寻找对应的loader去处理,如此往复,直到处理完毕所有依赖。

使用起来就是在modules字段中的rules(老版本名字为loaders,为保证兼容性,还是支持这个字段的)中配置test,去匹配文件(js、css、图片资源等等),然后把这个文件交给合适的loader处理即可,所以但凡新出的框架,如果用到了独特的语法功能,都会配套提供对应的loader工具

使用ES6等新特性

目前虽然浏览器对ES6新特性的支持度都非常高,但仍是有部分场景下,我们只能运行ES5的代码,这时候就需要利用到js转码届的特斯拉Bebel及其插件了

yarn add babel-loader babel-core babel-preset-env
  module: {
    rules: [
      {
        test: /.js$/,
        exclude: /(node_modules|bower_components)/,
        use: {
          loader: "babel-loader",
          options: {
            presets: ["env"]
          }
        }
      }
    ]
  },

显然test中匹配了所有的js文件,exclude字段去除了项目中依赖库里的文件,use则是配置对应的loader。其中对于options,是作为参数传递给babel-loader的,babel的相关参数可以参考babel的官方网站,其中presets作为最主要的参数,告诉babel按照那种规则去解析代码,当然env是一个组合,包含es2015、es2016等,当presets参数包含多个值时,babel的处理规则是倒序的,"es2017","es2016",babel会先去匹配es2016的规则。另外,对于babel的配置,也可以通过在根目录建立.babelrc方式去配置:

//.babelrc
{
  "presets": [
    "env"
  ]
}

当然除了扩展js的语法,有时候我们还需要扩展js的功能,比如在某些低版本的浏览器上运行"Hello World".includes("Hello"),这可以使用babel-polyfill这个组件,点击这里可以了解其使用方法。

CSS

当然,我们可以只让webpack处理js文件,继续在html文件中通过link标签引入css文件。但显然webpack希望前端攻城狮们也能像模块化编写js一样,进行css的编写。我们在src下创建css目录,并创建main.css文件

/* src/css/main.css */
body{
  background-color: deepskyblue; /* 我喜欢填空的蓝色 */
}
//src/index.js
import style from "./css/main.css"

这时候使用npm run build会发现编译报错,这是因为webpack无法处理载入main.css后的代码

ERROR in ./src/css/main.css
Module parse failed: Unexpected token (1:4)
You may need an appropriate loader to handle this file type.
| body{
|   background-color: red;
| }
 @ ./src/index.js 3:12-37

webpack官方提供css-loader专门处理导入的css模块,当然css-loader也仅仅是完成了模块的导入处理,使webpack在编译时候不再报错,实际上,还需要style-loader处理导入后的css数据,自动添加到html的style标签中。如果你在测试过程中,仅仅添加css-loaderloader,你会发现body实际上并没有变成背景蓝

yarn add style-loader css-loader
//config webpack.config.js module->rules
      {
        test: /.css$/,
        use: [
          {
            loader: "style-loader"
          }, {
            loader: "css-loader",
            options: {
              modules: true,
              localIdentName: "[path][name]__[local]--[hash:base64:5]"
              }
          }
        ]
      }

当然css-loader还有一个重要配置选项,modules参数,它支持导入的css中的类名自动重命名,这样即使在不同组件使用了相同的css类命名,经处理后互相之间也不会出现影响。看下效果:

CSS预编译工具

比较常见的预编译工具也就是Sass、Less、Stylus。在使用这三个预编译工具前,需要安装其对应的专属处理程序,比如Sass的处理工具node-sass。为了配合与webpack使用,还要安装对应的loader,比如sass-loader

yarn add sass-loader node-sass --dev

在webpack.config.js中添加对scss(Sass 3引入的新的语法格式,推荐新项目都用此)文件的支持,再起强调,webpack中loader执行顺序是从右往左,从下往上。

// webpack.config.js
module.exports = {
    ...
    module: {
        rules: [{
            test: /.scss$/,
            use: [{
                loader: "style-loader"
            }, {
                loader: "css-loader",
                options: {
                  modules: true,
                  localIdentName: "[path][name]__[local]--[hash:base64:5]"
              }
            }, {
                loader: "sass-loader"
                }
            }]
        }]
    }
};

测试一下:

/* src/sass/main.scss  */
body {
  background-color: blue;
}
.main {
  background-color: grey
}
//src/index.js
// import style from "./css/main.css"
import style from "./sass/main.scss"
let es6 = () => {
  console.log("run in es6")
  console.log(style)
}
es6();

其他两种预编译器可以参考各自官方文档:less-loader、stylus-loader

像处理JS一样处理CSS -- PostCSS

PostCSS官网介绍是,利用js转译css的一个工具。对PostCSS的介绍,我比较认可这篇文章,PostCSS提供了一个解析器,把css转换为抽象语法树(AST),当然这个AST是能够被js处理的,然后交给各种插件处理后,再将AST转为css代码,所以关键是这些插件能完成哪些功能。

Autoprefixer

Autoprefixer 是一个流行的 PostCSS 插件,其作用是为 CSS 中的属性添加浏览器特定的前缀。

cssnext

cssnext 插件允许开发人员在当前的项目中使用 CSS 将来版本中可能会加入的新特性。需要注意这个插件本身包含了Autoprefixer功能,所以如果使用了这个插件,则不需要Autoprefixer插件。
当然PostCSS中其实也包含支持Sass、Less等的插件,这个看个人喜好,不过我本人倒是蛮期待尝试下cssnext,毕竟大部分特性可能就是未来css支持的特性,提前熟悉下也算是预习。
更多插件可以读这篇文章,不再叙述

安装postcss-loader及其插件autoprefixer

yarn add postcss-loader autoprefixer --dev

webpack.config.js中添加postcss支持,注意postcss处理css文件的位置

// webpack.config.js
module.exports = {
    ...
    module: {
        rules: [{
            test: /.scss$/,
            use: [{
              loader: "style-loader"
            }, {
              loader: "css-loader",
              options: {
                modules: true,
                localIdentName: "[path][name]__[local]--[hash:base64:5]",
                minimize: false
              }
            }, {
              loader: "postcss-loader",
              options: {
                config: {
                  path: path.resolve(base, "config", "postcss.config.js")
                }
              }
            }, {
              loader: "sass-loader"
            }]
        }]
    }
};

如上面配置,postcss需要多带带的配置文件,创建config/postcss.config.js,添加如下配置:

//config/postcss.config.js
module.exports = {
  plugins: {
    "autoprefixer": {}
  }
}
图片资源

file-loader提供了对图片资源的loader功能,并且利用 publicPath 选项还能很好的支持cdn,配合url-loader还能对小图片直接进行数字序列化(DataURL),减少网络请求,提高加载速度。

yarn add url-loader file-loader --dev

增加相关配置;url-loader的默认 fallback loader就是 file-loader 为了更直观就写了出来,传递给 file-loader 的参数也只需要写在options中即可。这样
background-image: url("../image/logo.jpg") 当我们在css文件中使用这种方式引入图片时,就会触发url-loader去处理

//config/webpack.config.js
module.exports = {
    ...
    module: {
      rules: [    
      {
        test: /.(png|jpg|gif)$/,
        use: [
          {
            loader: "url-loader",
            options: {
              limit: 8192,
              fallback: "file-loader",
              name: "[hash:5].[ext]",
              // publicPath: "https://cdn.j2do.com/",
              outputPath: "images/"
            }
          }
        ]
      }
    ]
  }
}
虽不是万能,但却异常强大的插件(Plugins)系统

Loader是专注于处理Webpack导入的资源模块,而插件是对Webpack功能的扩展。除了Webpack内置的插件,开发社区提供了大量优秀的插件。当然插件也是解决问题的,我们还是以问题为导向,去介绍几款插件。

利用 extract-text-webpack-plugin 分离css到独立文件
yarn add extract-text-webpack-plugin --dev

其中ExtractTextPlugin中fallback是指定了如果不需要提取到独立css文件中的样式文件,则交给 style-loade 处理,其他loader配置,跟之前没有差异。同样如果是预编译文件(Sass、Less等)的话,也只需要增加对应的loader即可

module.exports = {
  ...
  module: {
    rules: [
      {
        test: /.css$/,
        use: ExtractTextPlugin.extract({
          fallback: "style-loader",
          use: [
            {
              loader: "css-loader",
              options: {
                modules: true,
                localIdentName: "[path][name]__[local]--[hash:base64:5]"
              }
            }, {
              loader: "postcss-loader",
              options: {
                config: {
                  path: path.resolve(base, "config", "postcss.config.js")
                }
              }
            }
          ]
        })
      }
    ]
  }
  ...
  plugins: [
    new ExtractTextPlugin("css/[name].css")
  ]
};

再次运行 yarn run build 会发现生成了独立的css文件

利用 html-webpack-plugin 自动生成 index.html 等入口文件

随着多入口以及css的分离,手动去写html的入口文件也是一件麻烦事,这时候 html-webpack-plugin 就排上用途了, html-webpack-plugin 能够根据某个模板文件自动生成入口的html,包括多个入口,安装及配置如下:

yarn add html-webpack-plugin --dev
module.exports = {
  ...
  plugins: [
    new ExtractTextPlugin("[name].css"),
    new HtmlWebpackPlugin({
      title: "Index Page",
      template: path.resolve(base, "src", "template/index.html.tmpl"),
      filename: "index.html",
      chunks: ["index"]
    }),
    new HtmlWebpackPlugin({
      title: "Main Page",
      template: path.resolve(base, "src", "template/index.html.tmpl"),
      filename: "main.html",
      chunks: ["main"]
    }),
  ]
};

创建模板文件,注意之所以命名为 .tmpl 是为了防止 .html 可能会被loader解析,<%= ... %> 将不会被插件识别,完成变量替换。如下图,我们的 title 是以变量形式在 webpack.config.js 中配置。该插件还支持多种模板文件,具体可见官方文档






  
  
    <%= htmlWebpackPlugin.options.title %>
  





代码优化压缩

前面已经将js、css等分离到多带带文件,接下来就是优化压缩这些代码。

对于css而言,只需要配置 css-loaderminimize 参数为 true 即可;当然还可以利用postcss的 cssnano 插件进行代码的压缩和优化,据说 cssnano 是目前css压缩优化中效果最好的工具。

对于js的压缩,我们借助于 uglifyjs-webpack-plugin 插件

yarn add uglifyjs-webpack-plugin --dev
module.exports = {
  ...
  plugins: [
    new ExtractTextPlugin("css/[name].css"),
    new HtmlWebpackPlugin({
      title: "Index Page",
      template: path.resolve(base, "src", "template/index.html.tmpl"),
      filename: "index.html",
      chunks: ["index"]
    }),
    new UglifyJsPlugin()
  ]
};
借助Eslint自动代码规范检测及格式化

Eslint不再介绍,在webpack下使用Eslint需要如下依赖包:

yarn add eslint eslint-loader babel-eslint eslint-config-standard --dev

其中eslint是必须的,eslint-loader 是连接eslint与webpack的loader,babel-eslint 是一个eslint解析器,使其能支持es6等语法检测,eslint-config-standard是Airbnb的规范配置,目前最流行的js规范。

安装过程中如果有提示eslint-config-standard@11.0.0-beta.0" has unmet peer dependency "eslint-plugin-import@>=2.2.0"等等,直接安装对应的包即可,比如:yarn add eslint-plugin-import --dev即可
eslint是规范js语法,所以他需要处理的是js文件,而且应该是先于所有loader去处理js文件,如果出错或者不规范则纠正之,这里可以利用webpack的 enforce 属性,设置eslint检查,先于其他loader
module.exports = {
  ...
  module: {
    rules: [
      {
        enforce: "pre",
        test: /.js$/,
        exclude: /node_modules/,//注意不要检测node_modules里面的代码
        loader: "eslint-loader",
        options: {
          fix: true //自动修复不规范的代码,并不是所有代码都能自动修复的,一些缩进,引号等能直接处理
        }
      }
      ...
    ]
  }
}

同时还要为eslint增加对应的配置文件 .eslintrc

{
  "parser": "babel-eslint",
  "extends": "standard",
  "rules": {}
}

这时候再尝试build吧,会发现一些不规范的代码被自动修复,当然有些不规范的代码,无法自动修复的,会直接导致错误;比如,声明了一个函数,却没有使用!

未完待续~~(近几天更新)

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/90495.html

相关文章

  • Python字符串的格式化,看这一篇就够了

    摘要:相信很多人在格式化字符串的时候都用的语法,提出一种更先进的格式化方法并成为的标准用来替换旧的格式化语法,从开始已经实现了这一方法其它解释器未考证。 showImg(https://segmentfault.com/img/remote/1460000018650325); 相信很多人在格式化字符串的时候都用%s % v的语法,PEP 3101 提出一种更先进的格式化方法 str.for...

    BDEEFE 评论0 收藏0
  • WorkManager从入门到实践,有这一篇就够了

    摘要:前言上一次我们对的应用进行了一次全面的分析,这一次我们来聊聊。 showImg(https://segmentfault.com/img/remote/1460000020077803?w=1280&h=853); 前言 上一次我们对Paging的应用进行了一次全面的分析,这一次我们来聊聊WorkManager。 如果你对Paging还未了解,推荐阅读这篇文章: Paging在Recy...

    bingchen 评论0 收藏0
  • Spring Boot 2.x(十四):整合Redis,看这一篇就够了

    摘要:丰富的特性还支持通知过期等等特性。到这个就说明测试通过了。主要针对方法配置,能够根据方法的请求参数对其进行缓存,常用于查询操作主要针对方法配置,能够根据方法的请求参数对其进行缓存,常用于修改操作清空缓存,主要用于删除操作。 [TOC] Redis简介 Redis 是一个开源的使用 ANSI C 语言编写、遵守 BSD 协议、支持网络、可基于内存亦可持久化的日志型、Key-Value 数...

    Miracle 评论0 收藏0
  • JSON入门看这一篇就够了

    摘要:采用完全独立于任何程序语言的文本格式,使成为理想的数据交换语言为什么需要提到,我们就应该和来进行对比。也是一种存储和交换文本信息的手段。那么好在哪里呢比更小更快,更易解析。使用的时候,也支持将转成但是,我们不一定使用框架来做开发呀。 什么是JSON JSON:JavaScript Object Notation 【JavaScript 对象表示法】 JSON 是存储和交换文本信息的语法...

    gplane 评论0 收藏0
  • Scrapy详解 爬虫框架入门看这一篇就够了

    摘要:目录前言架构安装第一个爬虫爬取有道翻译创建项目创建创建解析运行爬虫爬取单词释义下载单词语音文件前言学习有一段时间了,当时想要获取一下百度汉字的解析,又不想一个个汉字去搜,复制粘贴太费劲,考虑到爬虫的便利性,这篇文章是介绍一个爬虫框架, 目录 前言 架构 安装 第一个爬虫:爬取有道翻译 创建项目 创建Item 创建Spider 解析 运行爬虫-爬取单词释义 下载单词语音文件 ...

    lordharrd 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<