在开始之前检查node版本,确保node是最新的版本。使用旧版本,你可能遇到各种问题,因为它们可能缺少 webpack 功能以及/或者缺少相关 package 包。

// 检查node版本
node -v
v8.11.1
mkdir webpack-demo && cd webpack-demo

 

起步

  首先创建一个文件夹webpack4.0-demo,并初始化npm,然后本地安装webpack和webpack-cli。

mkdir webpack4.0-demo && cd webpack4.0-demo
// 初始化npm,并全部使用默认值
npm init -y

本地安装

  使用4+版本需要安装webpack-cli,

 cnpm install --save-dev webpack webpack-cli

目录结构

src/index

要在 index.js 中打包 lodash 依赖,我们需要在本地安装 library:

cnpm install --save-dev lodash
import _ from 'lodash'

function creatEl(){
    let el = document.createElement('div');

    // Lodash(目前通过一个 script 脚本引入)对于执行这一行是必需的
    el.innerHTML = _.join(['Hello', 'webpack'], ' ');

    return el
}

document.body.appendChild(creatEl())

dist/index.html

<!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>webpack</title>
</head>
<body>
    <script src="./main.js"></script>
</body>
</html>

package.json

{
  "name": "webpack4.0-demo",
  "version": "1.0.0",
  "description": "",
  "private": true,  // 删除入口文件,确保我们安装包是私有的(private)
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "lodash": "^4.17.11",
    "webpack": "^4.19.0",
    "webpack-cli": "^3.1.0"
  }
}

  执行webpack或者npx webpack,会将脚本作为入口文件,然后输出为main.js。Node 8.2+ 版本提供的 npx 命令,可以运行在初始安装的 webpack 包(package)的 webpack 二进制文件。

  在浏览器中打开 index.html,如果一切访问都正常,你应该能看到以下文本:'Hello webpack'。

使用配置文件

  在 webpack 4 中,可以无须任何配置使用,然而大多数项目会需要很复杂的设置,这就是为什么 webpack 仍然要支持 配置文件。这比在终端(terminal)中手动输入大量命令要高效的多,所以让我们创建一个取代以上使用 CLI 选项方式的配置文件。

  在主目录先添加一个webpack.config.js文件。

webpack.config.js

const path = require('path');

module.exports = {
    entry: './src/index.js',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'dist')
    }
}

package.json

{
  "name": "webpack4.0-demo",
  "version": "1.0.0",
  "description": "",
  "private": true,
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack"  // 新加代码
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "lodash": "^4.17.11",
    "webpack": "^4.19.0",
    "webpack-cli": "^3.1.0"
  }
}  

  将index.html中的script标签的src改成bundle.js。现在,可以使用 npm run build 命令,来替代我们之前使用的 npx 命令。注意,使用 npm 的 scripts,我们可以像使用 npx 那样通过模块名引用本地安装的 npm 包。

管理资源

加载css

  为了从 JavaScript 模块中 import 一个 CSS 文件,需要在 module 安装并添加 style-loader 和 css-loader。

cnpm install --save-dev css-loader style-loader

webpack.config.js

const path = require('path');

module.exports = {
    entry: './src/index.js',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'dist')
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    'style-loader',
                    'css-loader'
                ]
            }
        ]
    }
}
webpack 根据正则表达式,来确定应该查找哪些文件,并将其提供给指定的 loader。在这种情况下,以 .css 结尾的全部文件,都将被提供给style-loader和css-loader。
这使你可以在依赖于此样式的文件中 import 'style.css'。现在,当该模块运行时,含有 CSS 字符串的 <style> 标签,将被插入到 html 文件的<head>中。

在src文件夹下添加一个style.css文件

src/style.css

.hello{
    color: red;
}

src/index.js

import _ from 'lodash'
import './style.css'
function creatEl(){
    let el = document.createElement('div');

    // Lodash(目前通过一个 script 脚本引入)对于执行这一行是必需的
    el.innerHTML = _.join(['Hello', 'webpack'], ' ');

    el.className = 'hello';

    return el
}

document.body.appendChild(creatEl())

   重新执行命令,在浏览器中打开index.html可以看到字体变成红色。

加载图片

  加载图片需要先安装file-loader

cnpm install --save-dev file-loader

webpack.config.js

const path = require('path');

module.exports = {
    entry: './src/index.js',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'dist')
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    'style-loader',
                    'css-loader'
                ]
            },
            {
                test: /\.(jpg|png|svg|gif)$/,
                use: [
                    'file-loader'
                ]
            }
        ]
    }
}

src/index.js

import _ from 'lodash'

import "./style.css"
import Icon from './01.png'

function creatEl(){
    let el = document.createElement('div');

    // Lodash(目前通过一个 script 脚本引入)对于执行这一行是必需的
    el.innerHTML = _.join(['Hello', 'webpack'], ' ');

    el.className = 'hello';

    let img = new Image();
    img.src = Icon
    el.appendChild(img)
    return el
}

document.body.appendChild(creatEl())

 加载字体

  file-loader 和 url-loader 可以接收并加载任何文件,然后将其输出到构建目录。这就是说,我们可以将它们用于任何类型的文件,包括字体。让我们更新 webpack.config.js 来处理字体文件:

webpack.config.js

const path = require('path');

module.exports = {
    entry: './src/index.js',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'dist')
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    'style-loader',
                    'css-loader'
                ]
            },
            {
                test: /\.(jpg|png|svg|gif)$/,
                use: [
                    'file-loader'
                ]
            },
            {
                test: /\.(woff|woff2|eot|ttf|otf)/,
                use: [
                    'file-loader'
                ]
            }
        ]
    }
}

 输出管理

  到目前为止,我们在index.html文件中手动引入所有资源,然而随着应用程序增长,并且一旦对文件名使用哈希并输出多个bundle,手动的对index.HTML文件进行管理,一切就会变得困难起来。然而,我们可以通过使用一些插件,会使这个过程变得更容易操控。

  首先,要在src文件夹下新增一个print.js文件。

 

src/print.js

export default function print(){
    console.log('hello webpack')
}

src/index.js

import _ from 'lodash'

import "./style.css"
import Icon from './01.png'
import print from './print' function creatEl(){ let el = document.createElement('div'); // Lodash(目前通过一个 script 脚本引入)对于执行这一行是必需的 el.innerHTML = _.join(['Hello', 'webpack'], ' '); el.className = 'hello';
let btn
= document.createElement('button'); btn.innerHTML = 'click me' btn.onclick = print;
let img
= new Image(); img.src = Icon el.appendChild(img) el.appendChild(btn) return el } document.body.appendChild(creatEl())

webpack.config.js

  安装 html-webpack-plugin 插件。

cnpm install --save-dev html-webpack-plugin

调整webpack.config.js文件

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

module.exports = {
    devtool: 'source-map',
    entry: {
        app: './src/index.js',
     print: './src/print.js' }, output: { filename: '[name].bundle.js', path: path.resolve(__dirname, 'dist') }, module: { rules: [ { test: /\.css$/, use: [ 'style-loader', 'css-loader' ] }, { test: /\.(jpg|png|svg|gif)$/, use: [ 'file-loader' ] }, { test: /\.(woff|woff2|eot|ttf|otf)/, use: [ 'file-loader' ] } ] }, plugins: [ new HtmlWebpackPlugin({ title: 'webpack',   // 生成的HTML文件的标题 template: path.resolve(__dirname, 'index.html')  // 使用的模板路径 }) ] }

   运行程序可以看到build文件夹下会生成一个index.html文件。所有的bundle文件都会被引入到index文件中。

清楚dist文件夹

  由于经常修改,dist文件夹下会变得非常混乱webpack 会生成文件,然后将这些文件放置在 /dist 文件夹中,但是 webpack 无法追踪到哪些文件是实际在项目中用到的。通常,在每次构建前清理 /dist 文件夹,是比较推荐的做法,因此只会生成用到的文件。

  首先安装插件:

cnpm install clean-webpack-plugin --save-dev
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');

module.exports = {
    devtool: 'source-map',
    entry: {
        app: './src/index.js',
     print: './src/print.js' }, output: { filename: '[name].bundle.js', path: path.resolve(__dirname, 'dist') }, module: { rules: [ { test: /\.css$/, use: [ 'style-loader', 'css-loader' ] }, { test: /\.(jpg|png|svg|gif)$/, use: [ 'file-loader' ] }, { test: /\.(woff|woff2|eot|ttf|otf)/, use: [ 'file-loader' ] } ] }, plugins: [ new CleanWebpackPlugin(['dist']), new HtmlWebpackPlugin({ // 用于生成的HTML文档的标题 title: 'webpack', // 使用的模板路径 template: path.resolve(__dirname, 'index.html') }) ] }

运行程序会看到先删除dist文件夹,然后会再次生成。

错误追踪

  在使用webpack打包源代码时,会很难追踪到错误和警告在源代码中的原始位置。比如,将三个源文件(a.js、b.js和c.js)打包到一个bundle中,而其中一个源文件包含一个错误,那么堆栈跟踪就会简单的指向bundle.js。为了更容易的追踪错误和警告,我们可以使用source map功能,将编译后的代码映射回原始源代码。

webpack.config.js

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

module.exports = {
    devtool: 'source-map',
    entry: {
        app: './src/index.js',
     print: './src/print.js' }, output: { filename: '[name].bundle.js', path: path.resolve(__dirname, 'dist') }, module: { rules: [ { test: /\.css$/, use: [ 'style-loader', 'css-loader' ] }, { test: /\.(jpg|png|svg|gif)$/, use: [ 'file-loader' ] }, { test: /\.(woff|woff2|eot|ttf|otf)/, use: [ 'file-loader' ] } ] }, plugins: [ new CleanWebpackPlugin(), new HtmlWebpackPlugin({ // 用于生成的HTML文档的标题 title: 'webpack', // 使用的模板路径 template: path.resolve(__dirname, 'index.html') }) ] }

src/print.js

export default function print(){
    console.log('hello webpack');
    console.error('error');
}

点击按钮可以看到

更多详细内容,查看devtool

模块热更新

  每次编译代码都要手动执行一次npm run build,这样变得很麻烦。webpack中有不同几个选项,可以在代码发生变化时自动编译代码:

  1. webpack's Watch Mode
  2. webpack-dev-server
  3. webpack-dev-middleware

但是在大多数场景中使用的是webpack-dev-server。

观察者模式

添加一个用于启动 webpack 的观察模式的 npm script 脚本:

package.json

{
  "name": "webpack4.0-demo",
  "version": "1.0.0",
  "description": "",
  "private": true,
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "watch": "webpack --watch",
    "build": "webpack"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "clean-webpack-plugin": "^0.1.19",
    "css-loader": "^1.0.0",
    "express": "^4.16.3",
    "file-loader": "^2.0.0",
    "html-webpack-plugin": "^3.2.0",
    "lodash": "^4.17.11",
    "style-loader": "^0.23.0",
    "webpack": "^4.19.0",
    "webpack-cli": "^3.1.0"
  }
}

  现在,运行npm run watch,就会看到webpack编译代码后并不会退出。这是因为 script 脚本还在观察文件。现在,webpack 观察文件的同时,我们先移除我们之前引入的错误:

src/print.js

export default function print(){
    console.log('hello webpack');
    console.log('hello world');
}

然后刷新页面,可以看到:

唯一的缺点是,为了看到修改后的实际效果,你需要刷新浏览器。如果能够自动刷新浏览器就更好了,可以尝试使用 webpack-dev-server,恰好可以实现我们想要的功能。

webpack-dev-server

webpack-dev-server 为你提供了一个简单的 web 服务器,并且能够实时重新加载(live reloading)。首先要安装插件

cnpm install --save-dev webpack-dev-server

webpack.config.js

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

module.exports = {
    devtool: 'source-map',
    entry: {
        app: './src/index.js',
        print: './src/print.js'
    },
    output: {
        filename: '[name].bundle.js',
        path: path.resolve(__dirname, 'dist')
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    'style-loader',
                    'css-loader'
                ]
            },
            {
                test: /\.(jpg|png|svg|gif)$/,
                use: [
                    'file-loader'
                ]
            },
            {
                test: /\.(woff|woff2|eot|ttf|otf)/,
                use: [
                    'file-loader'
                ]
            }
        ]
    },
    plugins: [
        new CleanWebpackPlugin(['dist']),
        new HtmlWebpackPlugin({
            // 用于生成的HTML文档的标题
            title: 'webpack',
            // 使用的模板路径
            template: path.resolve(__dirname, 'index.html')
        })
    ],
    devServer: {
        contentBase: path.join(__dirname, 'dist')
    }
}

以上配置告知 webpack-dev-server,在 localhost:8080 下建立服务,将 dist目录下的文件,作为可访问文件。添加一个 script 脚本,可以直接运行开发服务器(dev server):

package.json

{
  "name": "webpack4.0-demo",
  "version": "1.0.0",
  "description": "",
  "private": true,
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "watch": "webpack --watch",
    "start": "webpack-dev-server --open","build": "webpack"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "clean-webpack-plugin": "^0.1.19",
    "css-loader": "^1.0.0",
    "express": "^4.16.3",
    "file-loader": "^2.0.0",
    "html-webpack-plugin": "^3.2.0",
    "lodash": "^4.17.11",
    "style-loader": "^0.23.0",
    "webpack": "^4.19.0",
    "webpack-cli": "^3.1.0",
    "webpack-dev-middleware": "^3.3.0",
    "webpack-dev-server": "^3.1.8"
  }
}

现在,我们可以在命令行中运行 npm start,就会看到浏览器自动加载页面。如果现在修改和保存任意源文件,web 服务器就会自动重新加载编译后的代码。

webpack-dev-middleware

  webpack-dev-middleware 是一个容器(wrapper),它可以把 webpack 处理后的文件传递给一个服务器(server)。 webpack-dev-server 在内部使用了它,同时,它也可以作为一个单独的包来使用,以便进行更多自定义设置来实现更多的需求。接下来是一个 webpack-dev-middleware 配合 express server 的示例。

首先安装插件

cnpm install --save-dev express webpack-dev-middleware

接下来我们需要对 webpack 的配置文件做一些调整,以确保中间件(middleware)功能能够正确启用:

webpack.config.js

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

module.exports = {
    devtool: 'source-map',
    entry: {
        app: './src/index.js',
        print: './src/print.js'
    },
    output: {
        filename: '[name].bundle.js',
        path: path.resolve(__dirname, 'dist'),
        publicPath: '/'
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    'style-loader',
                    'css-loader'
                ]
            },
            {
                test: /\.(jpg|png|svg|gif)$/,
                use: [
                    'file-loader'
                ]
            },
            {
                test: /\.(woff|woff2|eot|ttf|otf)/,
                use: [
                    'file-loader'
                ]
            }
        ]
    },
    plugins: [
        new CleanWebpackPlugin(['dist']),
        new HtmlWebpackPlugin({
            // 用于生成的HTML文档的标题
            title: 'webpack',
            // 使用的模板路径
            template: path.resolve(__dirname, 'index.html')
        })
    ],
    devServer: {
        contentBase: path.join(__dirname, 'dist')
    }
}

publicPath 也会在服务器脚本用到,以确保文件资源能够在 http://localhost:3000 下正确访问。然后在项目根目录下添加 server.js 文件,下一步就是设置我们自定义的 express 服务:

server.js

const express = require('express');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');

const app = express();
const config = require('./webpack.config.js');
const complier = webpack(config);

app.use(webpackDevMiddleware(complier, {
    publicPath: config.output.publicPath
}));

app.listen(3000, function(){
    console.log('server is running')
})

package.json

{
  "name": "webpack4.0-demo",
  "version": "1.0.0",
  "description": "",
  "private": true,
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "watch": "webpack --watch",
    "start": "webpack-dev-server --open",
    "server": "node server.js",
    "build": "webpack"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "clean-webpack-plugin": "^0.1.19",
    "css-loader": "^1.0.0",
    "express": "^4.16.3",
    "file-loader": "^2.0.0",
    "html-webpack-plugin": "^3.2.0",
    "lodash": "^4.17.11",
    "style-loader": "^0.23.0",
    "webpack": "^4.19.0",
    "webpack-cli": "^3.1.0",
    "webpack-dev-middleware": "^3.3.0",
    "webpack-dev-server": "^3.1.8"
  }
}

现在,在你的终端执行 npm run server,然后打开浏览器跳转到http://localhost:3000,将会看到webpack程序已经运行。

模块热替换

  模块热替换(Hot Module Replacement 或 HMR)是 webpack 提供的最有用的功能之一。它允许在运行时更新各种模块,而无需进行完全刷新。

启用HMR

  启用此功能实际上相当简单。而我们要做的,就是更新 webpack-dev-server 的配置,和使用 webpack 内置的 HMR 插件。我们还要删除掉 print.js 的入口起点,因为它现在正被 index.js 模式使用。

webpack.config.js

 

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

module.exports = {
    devtool: 'source-map',
    entry: {
        app: './src/index.js'
    },
    output: {
        filename: '[name].bundle.js',
        path: path.resolve(__dirname, 'dist'),
        publicPath: '/'
    },
    plugins: [
        new CleanWebpackPlugin(['dist']),
        new HtmlWebpackPlugin({
            // 用于生成的HTML文档的标题
            title: 'webpack',
            // 使用的模板路径
            template: path.resolve(__dirname, 'index.html')
        }),
        new webpack.NamedModulesPlugin(),
        new webpack.HotModuleReplacementPlugin()
    ],
    devServer: {
        contentBase: path.join(__dirname, 'dist'),
        hot: true
    }
}

 

  注意,我们还添加了 NamedModulesPlugin,以便更容易查看要修补(patch)的依赖。在起步阶段,我们将通过在命令行中运行 npm start 来启动并运行 dev server。现在,我们来修改 index.js 文件,以便当 print.js 内部发生变更时可以告诉 webpack 接受更新的模块

 

index.js

 

import _ from 'lodash'

import print from './print'

function creatEl(){
    let el = document.createElement('div');

    // Lodash(目前通过一个 script 脚本引入)对于执行这一行是必需的
    el.innerHTML = _.join(['Hello', 'webpack'], ' ');

    el.className = 'hello';

    let btn = document.createElement('button');
    btn.innerHTML = 'click me'
    btn.onclick = print;
    el.appendChild(btn)
    return el
}

document.body.appendChild(creatEl())

if (module.hot) {
    module.hot.accept('./print.js', function(){
        console.log('Accepting the updated printMe module!');
        print();
    })
}

如需查看webpack更多配置详情,前往webpack配置

 

posted on 2018-09-19 13:18  qweradf  阅读(2251)  评论(0编辑  收藏  举报