一 前言

文章介绍了一个现代化的项目的webpack4环境是什么样的。这里只是介绍了基础的功能,如果需要详细的相关只是可以去webpack官网去查阅。
代码地址:github
环境特点:
1.使用了webpack-dev-middleware,在文件内容更改之后自动编译;
2.使用了webpack-hot-middleware,在热编译之后会自动刷新页面更改的内容,而不是刷新整个页面
3.使用了server.js文件来自己控制启动http服务,后期可以扩展简单的后端功能

文章将webpack的配置文件写成了三份:公用部分文件、开发环境文件、线上环境文件,具体在文章中会有详细的介绍。

注意:文章介绍很详细,适合新手入门使用,请耐心阅读。

二 正文

1.path相关内容

文档:http://nodejs.cn/api/path.html
我们经常用到的就是path.resolve和path.join,那么我们就讲一下两者的用法和区别:
相关文章:Difference between path.resolve and path.join invocation?

(1)path.join
path.join() 方法使用平台特定的分隔符把全部给定的 path 片段连接到一起,并规范化生成的路径。

path.join('/a', '/b') // Outputs '/a/b'
path.join('/foo', 'bar', 'baz/asdf', 'quux', '..'); // outputs '/foo/bar/baz/asdf'

summary:path.join通常用到的是简单的将字符串进行拼接。
(2)path.resolve
path.resolve() 方法会把一个路径或路径片段的序列解析为一个绝对路径
给定的路径的序列是从右往左被处理的,后面每个 path 被依次解析,直到构造完成一个绝对路径(就会停止解析)

例如,给定的路径片段的序列为:/foo、/bar、baz,则调用 path.resolve('/foo', '/bar', 'baz') 会返回 /bar/baz。

如果处理完全部给定的 path 片段后还未生成一个绝对路径,则当前工作目录会被用上。
生成的路径是规范化后的,且末尾的斜杠会被删除,除非路径被解析为根目录。
长度为零的 path 片段会被忽略。
如果没有传入 path 片段,则 path.resolve() 会返回当前工作目录的绝对路径。
例子:

path.resolve('/foo/bar', './baz');
// 返回: '/foo/bar/baz'

path.resolve('/foo/bar', '/tmp/file/');
// 返回: '/tmp/file

(3)work directory 与 dirname
工作目录和文件目录并不是一直相等,我们以'./src/view/index.js'文件为例:
文件目录是固定的,就是文件所在的目录:./src/view/index.js
工作目录是不确定的,查看当前所在目录:pwd

如果你是在./src/view/ 下执行的index.js,那么就和文件路径相同./src/view/index.js
但是如果你在./src下执行的index.js,那工作路径则是./src

2.从server.js谈起

我们要知道server.js都做了些什么:

1.开启一个http服务器
2.使用热编译中间件,实时编译修改过的内容
3.使用热替换,实时查看最新的页面UI
4.【扩展】可以做一些后端的东西

代码如下:

const http = require('http');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const webpackHotMiddleware = require('webpack-hot-middleware');

// 使用express启用一个服务器
const express = require('express');

// 引用开发环境下的webpack配置文件
const config = require('./webpack.dev');
const app = express();
const webpackConfig = webpack(config);
const devMiddlewareCompiler = webpackDevMiddleware(webpackConfig,{
    publicPath:config.output.publicPath
});
const hotMiddlewareCompiler = webpackHotMiddleware(webpackConfig,{
    log: false,
    heartbeat: 2000,
 })

app.use(devMiddlewareCompiler);// 使用热编译中间件
app.use(hotMiddlewareCompiler);// 使用热替换中间件

app.listen(8080,function(){
    console.log('Example app listening on port 8080!\n');
});

注意:为什么要把热编译的功能放在node里面呢?如果引用wepack-dev-server会自动管理热编译,它的原理也还是利用express开启了一个小型服务器,只不过我们看不到它。所以如果你要自己控制,并且想简单方便的在后端做点小东西,可以完全使用上面的方法。如果是后端比较重就不建议这么写了,你需要开启两台服务,通过代理的方式模拟进行前后端通信了。

3.webpack common

webpack.common,js配置了无论是开发还是发布都需要的东西,比如一些loader的转译,代码的打包压缩等。
具体代码如下:

const path = require('path');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: {
    app: './src/index.js' // 入口文件index.js
  },
  module:{
    rules:[{
        test:/\.css$/,
        use:[
          'style-loader',
          'css-loader'
        ],
    },
    {
       test: /\.(png|svg|jpg|gif)$/,
       use: [
         'file-loader'
       ]
     },
      {
           test: /\.(woff|woff2|eot|ttf|otf)$/,
           use: [
             'file-loader'
           ]
      }
  ],
},
  plugins: [
    new CleanWebpackPlugin(['dist']),
    new HtmlWebpackPlugin({
      title: 'Production'
    }),
  ],
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist'),
  }
};

下面讲一讲主要做了什么事:

3-1 file-loader

a. 加载图片
如果我们要想像引用模块那样引用一个图片,例如:

import Rose from './img/rose.jpg'

在 webpack 里负责图片翻译的是 file-loader。而且,webpack 在最终构建时,会自动将模块中引用的图片拷贝到相应目录。如果你检查此元素,你将看到实际的文件名已更改为像 5c999da72346a995e7e2718865d019c8.png 一样。这意味着 webpack 在 src 文件夹中找到我们的文件,并成功处理过它!

b. 加载字体
css文件中引用字体:

 @font-face {
   font-family: 'MyFont';
  src:  url('./my-font.woff2') format('woff2'),
         url('./my-font.woff') format('woff');
   font-weight: 600;
   font-style: normal;
 }

【扩展】file-loader与url-loader
如果我们希望在页面引入图片(包括img的src和background的url)。当我们基于webpack进行开发时,引入图片会遇到一些问题。其中一个就是引用路径的问题。拿background样式用url引入背景图来说,我们都知道,webpack最终会将各个模块打包成一个文件,因此我们样式中的url路径是相对入口html页面的,而不是相对于原始css文件所在的路径的。这就会导致图片引入失败。这个问题是用file-loader解决的,file-loader可以解析项目中的url引入(不仅限于css),根据我们的配置,将图片拷贝到相应的路径,再根据我们的配置,修改打包后文件引用路径,使之指向正确的文件。另外,如果图片较多,会发很多http请求,会降低页面性能。这个问题可以通过url-loader解决。url-loader会将引入的图片编码,生成dataURl。相当于把图片数据翻译成一串字符。再把这串字符打包到文件中,最终只需要引入这个文件就能访问图片了。当然,如果图片较大,编码会消耗性能。因此url-loader提供了一个limit参数,小于limit字节的文件会被转为DataURl,大于limit的还会使用file-loader进行copy。

//url-loader封装了file-loader。url-loader不依赖于file-loader,即使用url-loader时,只需要安装url-loader即可
// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.(png|jpg|gif)$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 8192
            }
          }
        ]
      }
    ]
  }
}

3-2 css loader与style-loader
如果你在JS中使用:

import './index.css'

我们需要 CSS 加载器:

(1) css-loader - 预处理 CSS 文件
(2) style-loader - 将 CSS 插入到 DOM 中的 style 标签

要查看 webpack 做了什么,请检查页面(不要查看页面源代码,因为它不会显示结果),并查看页面的 head 标签。它应该包含我们在 index.js 中导入的 style 块元素:<style>内容</style>。

注意:
(1)加载器的顺序是从后往前的,loader 的顺序很重要:如果把 style-loader 放到 css-loader 后面,我们就会撞见错误。
(2)我们如果只使用了 css-loader,则 webpack 只是将 CSS 文件预处理成模块然后打包到构建文件中,并不会插入到页面。

【扩展】将CSS单独打包:
webpack1/2/3:extract-text-webpack-plugin
webpack4:mini-css-extract-plugin

3-3 htm-lwebpack-plugin
详细介绍见:html-webpack-plugin npm
这是一个webpack插件,可以简化创建HTML文件以便为webpack包提供服务。 这对于webpack包来说特别有用,它在文件名中包含一个hash,用于更改每个编译。 你可以让插件为你生成一个HTML文件,使用lodash模板提供你自己的模板或使用你自己的加载器。

3-4 clean-webpack-plugin
详细介绍见:clean-wenpack-plugin
一个在创建之前清除你build文件夹的webpack插件。在打包生成新的build文件的时候清除之前生成的,这非常有用。

4.webpack development

两种环境的配置在webpack4中都支持mode的配置:development/production,具体的默认配置查询可以移步这里:webpack4 Mode的默认设置

webpack.dev.config.js:

 const webpack = require('webpack');
 const merge = require('webpack-merge');
 const common = require('./webpack.common.js');

 module.exports = merge(common, {
    mode:'development',
    plugins:[
      new webpack.NamedModulesPlugin(),
      new webpack.HotModuleReplacementPlugin(),
   ]
 });

这里做了两件事:
(1)mode:'development':定义环境为开发环境。在webpack4之后省了很多操作,只需要指定为开发环境,就会自动设定source map等信息
(2)HotModuleReplacementPlugin:模块热替换
使用热替换需要两步:

首先: Add the following plugins to the plugins array


plugins: [
    // OccurenceOrderPlugin is needed for webpack 1.x only
    new webpack.optimize.OccurenceOrderPlugin(),
    new webpack.HotModuleReplacementPlugin(),
    // Use NoErrorsPlugin for webpack 1.x
    new webpack.NoEmitOnErrorsPlugin()
]
Occurence ensures consistent build hashes, hot module replacement is somewhat self-explanatory, no errors is used to handle errors more cleanly.

其次:Add 'webpack-hot-middleware/client' into the entry array.

This connects to the server to receive notifications when the bundle rebuilds and then updates your client bundle accordingly.

5.webpack production

两种环境的配置在webpack4中都支持mode的配置:development/production,具体的默认配置查询可以移步这里:webpack4 Mode的默认设置

 const webpack = require('webpack');
 const path = require('path');
 const merge = require('webpack-merge');
//  const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
 const common = require('./webpack.common.js');


 module.exports = merge(common, {
     output:{
        // publicPath:path.resolve(__dirname, 'dist'),
     },
     mode:'production',
 });

指定环境为生产环境,默认开启UglifyJSPlugin。

【扩展】将文件标记为无副作用(side-effect-free)
src/math.js:

export function square(x) {
  return x * x;
}

export function cube(x) {
  return x * x * x;
}

src/index.js:

import { cube } from './math.js';

math.js文件的square函数没有被导入,但是,它仍然被包含在 bundle 中。
在一个纯粹的 ESM 模块世界中,识别出哪些文件有副作用很简单。然而,我们的项目无法达到这种纯度,所以,此时有必要向 webpack 的 compiler 提供提示哪些代码是“纯粹部分”。
这种方式是通过 package.json 的 "sideEffects" 属性来实现的。

{
  "name": "your-project",
  "sideEffects": false
}

如同上面提到的,如果所有代码都不包含副作用,我们就可以简单地将该属性标记为 false,来告知 webpack,它可以安全地删除未用到的 export 导出。
如果你的代码确实有一些副作用,那么可以改为提供一个数组:

{
  "name": "your-project",
  "sideEffects": [
    "./src/some-side-effectful-file.js"
  ]
}

三 后记


specialcoder
2.2k 声望170 粉丝

前端 设计 摄影 文学