WebPack的使用及思路

webpack每年升级, 本文基于webpack@4.43.0。

即使版本升级,每部分细节会有变化,本文思路依然有效。

webpack是干什么的

  • 转译代码

ES6转为ES5, SCSS转为CSS

  • 构建build
  • 代码压缩
  • 代码分析

安装webpack

  • webpack@4

如果webpack升级到了5,本文的思路依然有效

  • webpack-dev-server

用于本地预览

查看webpack版本:npm info webpack

在一个文件夹里引入webpack

Getting Started | webpack

mkdir webpack-demo
cd webpack-demo
npm init -y // 创建了package.json
yarn add webpack webpack-cli --dev // 多了一个node_modules目录

如何调用本地安装的webpack

./node_modules/.bin/webpack --version
  • 简易写法:
npx webpack

不够稳定,不能使用的话,就用上一种方法

Error: The 'mode' option has not been set

解决方法: Configuration | webpack

webpack配置entry和output

  • 设置webpage.config.js
// webpack.config.js
var path = require("path");

module.exports = {
  mode: "development", // 或"production"
  // 入口
  entry: "./src/index.js", 
  // 出口
  output: {
    // 转译后的js文件放在dist目录
    path: path.resolve(__dirname, "dist"),    
    // 转译后的js文件,例如:dist/main.8e786759cc25ae130389.js
    filename: "[name].[contenthash].js" // filename: "bundle.js"
  }
};

webpack插件自动生成HTML

  • 修改package.json
 "scripts": {
    "build": "rm -rf dist; webpack",
  }

之后可以通过yarn build或者npm run build构建

  • 安装HtmlWebpackPlugin

HtmlWebpackPlugin | webpack

npm install --save-dev html-webpack-plugin

改完之后再yarn build, 发现dist目录下已经有了index.html

示例代码

扩展阅读: 利用template生成HTML

webpack引入CSS

css-loader | webpack

  • 安装css-loader
npm install --save-dev css-loader
  • 安装style-loader
npm install --save-dev style-loader
  • 配置webpage.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: ['style-loader', 'css-loader'],
      },
    ],
  },
};

示例代码

使用webpack-dev-server

如果你觉得每次build很麻烦,可以使用webpack-dev-server来加快开发: Development | webpack

  • Setting mode to development
// webpack.config.js
module.exports = {
    mode: 'development',
}
  • Using source maps
// webpack.config.js
module.exports = {
    devtool: 'inline-source-map',
}
  • 安装webpack-dev-server
npm install --save-dev webpack-dev-server

webpack.config.js

module.exports = {
     devServer: {
      contentBase: './dist',
    },
}

package.json

  {
    "name": "development",
    "version": "1.0.0",
    "description": "",
    "private": true,
    "scripts": {
+     "start": "webpack-dev-server —open",
      "build": "webpack"
    },
  • 配置完成,自己试试吧~
yarn start

示例代码

使用插件提取CSS文件

MiniCssExtractPlugin | webpack

  • 安装mini-css-extract-plugin
npm install --save-dev mini-css-extract-plugin
  • 配置webpack.config.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
  plugins: [new MiniCssExtractPlugin()],
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: [MiniCssExtractPlugin.loader,'css-loader'],
      },
    ],
  },
};
  • 自己试试
yarn build

此时发现,dist目录下已经有生成的main.css,并且main.css已经被自动引入到index.html中了

  • 如何让main.cssmain.8e786759cc25ae130389.js一样,中间有个哈希值?
// webpack.config.js
module.exports = {
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].[hash].css',
      chunkFilename: '[id].[hash].css',
    }),
  ],

示例代码

更多配置: MiniCssExtractPlugin | Options

使用两个webpack config文件(多份webpack config配置)

开发环境(development)和生产环境(production)的webpack配置

development:

yarn start

production:

yarn build

为什么要有development和production两个mode

  • 开发环境和生产环境的构建目标是有很大的不同的。
  • 在开发环境中,为了便于代码调试以及实现浏览器实时更新,我们需要开启 source maplocalhost server
  • 在生产环境中,为了实现缓存优化以及改善加载时间,我们的目标转向于打包成更小的 bundlechunk,分离第三方包以及开启更轻量级的 source map以及更优化的资源。
  • 因此开发环境和生产环境需要单独配置 webpack。

start开发用,build打包给用户。(development开发用, production给用户用)

如何区分

Q: 上面看了那么多,既然是不同的命令,那能不能设置他们两个用不同的config?如果用不同的config,不就可以自由切换了?

A: 你可真是个小机灵鬼,那我们就试试吧。

  • 首先看看webpack有哪些用法:
npx webpack --help
  • 修改package.json

更改默认的配置文件,build的时候不使用默认的配置,而使用webpack.config.prod.js这个配置

{
  "scripts": {
    "start": "webpack-dev-server —open",
    "build": "rm -rf dist && webpack --config webpack.config.prod.js",
  },

注意将webpack.config.prod.js中的mode改为production

多份webpack config配置

Q: 我们现在使用了两个配置文件,分别是webpack.config.js用于开发模式,webpack.config.prod.js用于生产模式, 对吧?

A: 对。

Q: 那我觉得你有点傻,因为这两个文件基本都相同啊,你就不能简化一下?

A: 彳亍口巴,请继续看,我把它们拆成了三个文件

webpack.config.base.js

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
  entry: "./src/index.js",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "[name].[contenthash].js", // 默认[name] 为 main
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: "My App",
      template: "src/assets/index.html",
    }),
  ],
};

webpack.config.js

developmentconfig

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

const base = require("./webpack.config.base.js");

module.exports = {
  ...base, // 把base的所有属性搞过来
  devtool: "inline-source-map",
  devServer: {
    contentBase: "./dist",
  },
  mode: "development",
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: ["style-loader", "css-loader"],
      },
    ],
  },
};

webpack.config.prod.js

productionconfig

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

const base = require("./webpack.config.base.js");

module.exports = {
  ...base,
  mode: "production",
  plugins: [
    ...base.plugins,
    new MiniCssExtractPlugin({
      filename: "[name].[hash].css",
      chunkFilename: "[id].[hash].css",
    }),
  ],
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: [MiniCssExtractPlugin.loader, "css-loader"],
      },
    ],
  },
};

这样一来,利用继承,就清爽多了

示例代码

扩展阅读: GitHub - survivejs/webpack-merge: Merge designed for Webpack

webpack loader vs webpack plugin

  • loader是加载器,用来加载某些资源文件。比如说,babel-loader是用来加载高级的JS,把它变成浏览器支持的JScss-loaderstyle-loader是用来加载css的,把它变成页面中的style标签。
  • plugin是插件,用来扩展webpack的功能。比如html-webpack-plugin是用来生成HTML文件的,mini-css-extract-plugin则是用来抽取css的代码,把它合并成一个css文件的。

引入SCSS

sass-loader | webpack

  • 安装sass-loader
npm install sass-loader sass webpack —save-dev
  • 修改webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.s[ac]ss$/i,
        use: [
          // Creates `style` nodes from JS strings
          'style-loader',
          // Translates CSS into CommonJS
          'css-loader',
          // Compiles Sass to CSS
          'sass-loader',
        ],
      },
    ],
  },
};

注意,node-sass已经过时了,请使用 dart-sass(sass)

  • yarn start试试

示例代码

扩展阅读 GitHub - sass/dart-sass: The reference implementation of Sass, written in Dart.

引入LESS

less-loader | webpack

  • 安装less loader
npm install less-loader --save-dev
  • 修改webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.less$/,
        loader: 'less-loader', // compiles Less to CSS
      },
    ],
  },
};
  • Module parse failed: Unexpected token 解决办法

修改webpack.config.js:

module.exports = {
  module: {
    rules: [
      {
        test: /\.less$/,
        loader: ['style-loader', 'css-loader', 'less-loader'], 
      },
    ],
  },
};

这一步的目的是: 将less转成css,将css转成JS,再将JS转成style标签

Q: Error: Cannot find module 'less’解决办法?

A: yarn add less --dev装一下less不就好了

  • yarn start试试

示例代码

引入Stylus

GitHub - shama/stylus-loader: A stylus loader for webpack.

  • 安装less loader
npm install stylus-loader stylus --save-dev
  • 修改webpack.config.js
module: {
  loaders: [{
    test: /\.styl$/,
    loader: 'css-loader!stylus-loader?paths=node_modules/bootstrap-stylus/stylus/'
  }]
}

实际上这是一种过时的语法,正确的写法:

module: {
  loaders: [{
    test: /\.styl$/,
    loader: ['style-loader', 'css-loader', 'stylus-loader'], 
  }]
}
  • yarn start试试

示例代码

使用file-loader引入图片

file-loader | webpack

  • 安装file-loader
$ npm install file-loader --save-dev
  • 修改webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif)$/i,
        use: [
          {
            loader: 'file-loader', // 把文件变成文件路径
          },
        ],
      },
    ],
  },
};
  • yarn start试试

示例代码

webpack import() 懒加载

所谓懒加载,顾名思义,该加载的时候不加载,等不得不加载的时候再加载

Q: 如何实现懒加载?

A: 用import()去加载文件,然后我会得到一个promisepromise的前面写成功之后做什么,后面写失败之后做什么。

  • 例子:

index.js

const button = document.createElement("button");
button.innerHTML = "lazy load";
button.onclick = () => {
  const promise = import("./lazy");
  promise.then(
    (module) => {
      const fn = module.default;
      fn();
    },
    () => {
      console.log("Error in loading module");
    }
  );
};

lazy.js

export default function lazy() {
  console.log("I am a lazy load module");
}

示例代码

webpack一键部署到github

原始的方法–使用master分支

分两步,第一步是本地打包,第二部是上传到github

  • build一下
yarn build
  • 可以本地使用http-server测试一下
cd dist/
hs -c-1

没有http server的话,可以使用yarn add http-server安装一下

  • GitHub新建目录,然后上传到GitHub
git remote add origin URL
git push -u origin master
  • 进入项目的Settings > GitHub PagesSource改为项目所在的分支。

此时项目发布在 URL/dist/index.html

使用gh-pages分支

Q: 我不想用dist,我不希望用户看到/dist/index.html这么奇怪的路径,怎么办?

A: 彳亍口巴,那我们使用新分支。

  • 新建分支
git branch gh-pages
  • 进入这个分支
git checkout gh-pages
  • 删除除了dist,node_modules,.gitignore之外的所有文件
  • dist目录下的所有文件拷到当前目录并删除dist
mv dist/* ./
rm -rf dist
  • git add,git commit,git push素质三连

这样就可以平时在master分支开发,然后在gh-pages分支发布了

  • 进入项目的Settings > GitHub PagesSource改为gh-pages分支。
  • 稍等GitHub一会,就可以预览啦~

使用脚本简化

每次这么做也太累了⑧,使用脚本简化一下吧

  • 切回master分支
git checkout master
  • 新建deploy.sh, 写入:
yarn build &&
git checkout gh-pages &&
rm -rf *.html *.js *.css *.png &&
mv dist/* ./ &&
rm -rf dist;
git add . &&
git commit -m ‘update’ &&
git push &&
git checkout - # '-'代表之前的分支

这样以后就可以通过sh deploy.sh完成之前的所有功能了

经验:先提交再部署(deploy),是一个好习惯

查看本文所有示例的完整代码

comments powered by Disqus