Section-2 webpack的核心概念

section-2.1 什么是loader

loader 用于对模块的源代码进行转换。loader 可以使你在 import 或"加载"模块时预处理文件。因此,loader 类似于其他构建工具中“任务(task)”,并提供了处理前端构建步骤的强大方法。loader 可以将文件从不同的语言(如 TypeScript)转换为 JavaScript,或将内联图像转换为 data URL。loader 甚至允许你直接在 JavaScript 模块中 import CSS文件!
webpack默认只能打包js结尾的文件,因此当需要打包其他格式文件的时候,我们都需要安装对应的loader,并在webpack.config.js里写好配置规则,这里以图片为例
首先先下载file-loader,执行npm i file-loader -D,然后更改配置如下

module.exports = {
    mode: "none",
    entry: './src/index.js',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'dist')
    },
    module: {
        rules: [  //模块打包规则
            {
                test: /\.jpg$/,
                use: {
                    loader: 'file-loader'
                }
            }
        ]
    }
}

在index.js里引入图片模块

import Header from './header.js'
import Content from './content.js'
import Sidebar from './sidebar.js'
import avatar from './avatar.jpg'

let root = document.getElementById("root")

let header = new Header(root)
let content = new Content(root)
let sidebar = new Sidebar(root)

let img = new Image()
img.src = avatar
root.append(img)

执行npm run bundle,此时图片就被打包成功。注意,这里的模块返回的是打包后的图片路径,所以可以直接赋值给src。对到webpack,当匹配到.jpg结尾的格式时,会去使用file-loader,file-loader会先把图片移动到dist目录下,并赋予一个唯一的文件名,然后返回图片路径

section-2.2 使用loader打包静态资源(图片篇)

options参数,用来给打包的loader添加额外的规则,代码里写了注释这里就不写太多了

{
    test: /\.(jpg|png|gif)$/,
    use: {
        loader: 'file-loader',
        options: {
            //placeholder 占位符
            name: '[name]_[hash].[ext]',  //打包出来的图片名称是原图片名称跟后缀
            outputPath: 'images/'  //打包到images文件夹下,如果没有会创建该文件夹
        }
    }
}

这里插点额外知识,[]里除了占位符,还能使用 “:” 来附加规则,如下

{
  loader: 'file-loader',
  options: {
    name: '[sha512:hash:base64:7].[ext]'
  }
}
// 个人理解:用sha512这个字符的哈希值转成base64位取前7位,如有误欢迎大佬指正,这部分知识不太懂
gdyb21L.png

除了字符,甚至还能使用函数,来对不同环境制定不同规则:

{
  loader: 'file-loader',
  options: {
    name (file) {
      if (env === 'development') {
        return '[path][name].[ext]'
      }

      return '[hash].[ext]'
    }
  }
}

跟file-loader相似的还有一个url-loader,不同在于url-loader默认返回DataURL,因此需要设置limit来制定规则

{
    test: /\.(jpg|png|gif)$/,
    use: {
        // 如果图片比较小可以使用url-loader
        loader: 'url-loader',
        options: {
            name: '[name]_[hash].[ext]',  //打包出来的图片名称是原图片名称跟后缀
            outputPath: 'images/',        //打包到images文件夹下,如果没有会创建该文件夹
            limit: 2048  //是否小于2KB,当大于2KB的时候不要打包成DataURL
        }
    }
}

好处是会把图片返回成base64位文件,省了HTTP请求,但当图片太大的时候,会增加打包后的js文件大小,因此设置好loader,小图片打包成DataURL,大图片依然跟file-url一样打包到images文件夹下

section-2.3 & 2.4 使用 Loader 打包静态资源(样式篇)

打包css,必须使用下面两个loader npm i style-loader css-loader -D

  1. style-loader:负责把样式添加到head上
  2. css-loader:负责简析css语法
    两者在配置中的顺序不可颠倒,因为webpack解析是从右到左,从下到上,这节的内容这点需要注意,下面不再重复
{
    test: /\.css$/,
    use: ['style-loader', 'css-loader']
}

然而现在应该很少会直接写css,而是借助sass/less/stylus这些工具,所以下面以sass为例,先安装sass-loader,sass-loader需要两个包:sass-loader 和 node-sass
由于node-sass比较大,很容易因为网络问题下载不了(因为被墙),当时的报错我就不再特地复现然后截图了,如果安装不了的同学跟着我来就行了,正常都是因为被墙下载不了

  1. 安装淘宝npm镜像: npm install -g cnpm --registry=https://registry.npm.taobao.org
  2. 使用cnpm来安装:cnpm i sass-loader node-sass -D
    虽然我不写options可以直接写成数组,不过这里还是拿官网的来,因为这就是上面说的从下到上,还有注释,根据注释应该也能理解为什么顺序不能颠倒了
{
    test: /\.css$/,
    /* use: ['style-loader', 'css-loader', 'sass-loader'] */
    use: [
        {
            loader: "style-loader" // 将 JS 字符串生成为 style 节点
        },{
            loader: "css-loader"   // 将 CSS 转化成 CommonJS 模块
        },{
            loader: "sass-loader"  // 将 Sass 编译成 CSS
        }
    ]
}

对的css,我们一般会用 postcss 来给一些支持度还不够好的css3属性加前缀 npm i postcss-loader -D
postcss还需要一份postcss.config.js(又或者命名为.postcssrc.js也行)配置,所以在webpack.config.js同级目录下创建对应的配置
我们需要使用postcss的autoprefixer插件来给css3支持度不够好的属性加前缀,下载插件 npm i autoprefixer -D
这里可能有人跟我一样npm又是下载不了,所以也用cnpm吧:cnpm i autoprefixer -D,如果你不想每次都去下载插件,也能把postcss所有插件一次性下到node_modules里 npm i postcss-plugin -D

// postcss.config.js
module.exports = {
    plugins: [
        require('autoprefixer')
    ]
}

如果在sass文件中使用@import引入另外一个sass文件,那么需要让引入进来的sass文件也走一遍postcss和sass-loader进行处理,这个时候我们需要把 css-loader 写成对象,在里面添加 importLoaders 参数(具体的其他参数请查阅官网)

{
    test: /\.scss$/,
    use: [
        'style-loader',
        {
            loader: "css-loader",
            options: {
                // 查询参数 importLoaders,用于配置「css-loader 作用于 @import 的资源之前」有多少个 loader。
                // 0 => no loaders (default); 1 => postcss-loader; 2 => postcss-loader, sass-loader
                importLoaders: 2
            }
        },
        'sass-loader',
        'postcss-loader']
}

有些时候,我们并不想css样式全局使用,部分要使用私有化处理(比如vue中给style添加的scope),那么需要在css-loader里添加 modules 参数

{
    test: /\.scss$/,
    use: [
        'style-loader',
        {
            loader: "css-loader",
            options: {
                importLoaders: 2,
                modules: true     // css模块化,这个时候全局的 import index.sass将会失效
            }
        },
        'sass-loader',
        'postcss-loader']
}

对应的,index.js中对到sass文件也不能作为全局引入了,需要改写成以下方式

import avatar from './avatar.jpg'
import style from './index.scss'
// import './index.scss' 这种直接无效

let root = document.getElementById("root")

let img = new Image()
img.src = avatar
img.classList.add(style.avatar)  // 原来直接写的'avatar'将会失效,具体原因看下面打包后的截图
root.append(img)
样式私有化 - modules

可见avatar类名被编译成了_3WpIug-N0UMam_vssd03tO,所以说如果不写 style.avatar 的话,avatar类名将会无定义

对到css中用了字体文件的(或者icon-font),会加载诸如ttf/svg/eot等等这样后缀的名字,这个时候需要使用上面的file-loader,因此为了简单化,图片使用url-loader,字体文件使用file-loader

{
    test: /\.(woff|eot|ttf|svg)$/,
    use: {
        loader: 'file-loader',
        options: {
            name: '[name]_[hash].[ext]',
            outputPath: 'icon-font/'
        }
    }
},
{
    test: /\.(jpg|png|gif)$/,
    use: {
        loader: 'url-loader',
        options: {
            name: '[name]_[hash].[ext]',
            outputPath: 'images/',
            limit: 20480  //是否小于20KB
        }
    }
}

section-2.5 使用 plugins 让打包更便捷

插件(plugins)

loader 被用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量。插件接口功能极其强大,可以用来处理各种各样的任务。

想要使用一个插件,你只需要 require() 它,然后把它添加到 plugins 数组中。多数插件可以通过选项(option)自定义。你也可以在一个配置文件中因为不同目的而多次使用同一个插件,这时需要通过使用 new 操作符来创建它的一个实例。
为了解决手动把index.html加入到dist目录里,可以使用 html-webpack-plugin 插件来指定默认模板,使用clean-webpack-plugin 来实现每次打包之前删除dist目录。执行npm i html-webpack-plugin clean-webpack-plugin -D进行安装,webpack.config.js添加plugin参数

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

plugins: [
    new HtmlWebpackPlugin({
        template: './index.html'      // 以index.html作为html模板
    }),
    // 打包之前删除dist目录,现在的新版是直接取output路径,不需要自己写删除哪个打包后的文件夹
    new CleanWebpackPlugin()
]

Section-2.6 entry 与 output 的基础配置

entry可以支持多入口文件,比方说我们要在页面手动添加jq与逻辑代码

entry: {
    jquery: './src/jquery.js',
    main: './src/index.js'
}

此时output也需要对应的更改一下,不可将name写固定,否则会因为文件重名而打包失败,必须不写或使用占位符的方式

output: {
    filename: '[name].js',
    path: path.resolve(__dirname, 'dist')  //__dirname指的是webpack.config.js这个文件所在的路径
}

这样打包出来的文件就会如下:

<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript" src="main.js"></script>

真正项目中,我们会给s文件添加cdn域名,这里只作为一个结果展示,所以使用的localhost:8080

output: {
    filename: '[name].js',
    path: path.resolve(__dirname, 'dist'),  //__dirname指的是webpack.config.js这个文件所在的路径
    publicPath: 'localhost:8080'     //给js添加cdn地址,这里没有所以用这个来看看
}
<script type="text/javascript" src="localhost:8080/jquery.js"></script>
<script type="text/javascript" src="localhost:8080/main.js"></script>

Section-2.7 sourceMap的配置

当我们进行开发的时候,如果代码出现问题,浏览器控制台会进行提示。然而打包后的文件报错行数并不是我们真正开发的文件,这个时候我们就需要使用sourceMap来建立一个映射关系,好让我们知道出问题的实际上是哪一个文件
在webpack.config.js文件中添加devtool参数,此选项控制是否生成,以及如何生成 source map。

module.exports = {
    mode: "development",
    devtool: 'cheap-module-eval-source-map',
    entry: './src/index.js',
    output: {
        filename: '[name].js',
        path: path.resolve(__dirname, 'dist')
    }
}

对到该配置对应的一些关键词:
source-map sourceMap 是一个映射关系,能直接映射到对应的代码文件
cheap 报错不精确到哪一行哪一列,只报错到行
inline 不生成map文件,直接以data64的形式写进打包后的js代码里
module 对到第三方依赖,loader也进行报错
eval 会生成一个eval()函数,把错误写到eval中,性能是最快的
因此,对到开发环境和线上环境,我们需要写不一样的配置参数
development 开发环境一般配置为: devtool: 'cheap-module-eval-source-map'
production 正式环境一般配置为: devtool: 'cheap-module-source-map'
关于sourceMap的实现,可以阅读以下文章:
打破砂锅问到底:详解Webpack中的sourcemap
Introduction to JavaScript Source Maps
JavaScript Source Map 详解
需翻墙的油桶视频

Section-2.8 使用 WebpackDevServer 提升开发效率

当我们每次更改完代码,总需要去执行npm run bundle,这样太麻烦,所以需要让webpack监听打包文件,这个时候我们需要在 package.json里去修改webpack,添加 --watch来监听打包文件,让webpack重新打包

"scripts": {
    "bundle": "webpack",
    "watch": "webpack --watch"
  }

对到本地开发,需要自己去请求json打桩数据,这个时候就必须开启一个服务器,而webpack-dev-server就能为我们构建一个本地服务器
在webpack.config.json里添加 devServer,更多的参数请直接查阅官网,webpack并没有自带webpack-dev-server,所以需要安装 npm i webpack-dev-server -D

devServer: {
    // 服务器开启在哪个文件夹下,因为我们output写的是dist,所以在此目录下创建本地服务器
    contentBase: './dist',
    open: true,  // 打包完成后自动打开浏览器
    proxy: {  // 接口代理,参考另一篇vue模拟去哪儿网的文章,里面去请求的json请求全都走了代理
        '/api': 'http://localhost:80'
    }
}

此时在package.json里添加一条start命令来启动webpack-dev-server

"scripts": {
    "bundle": "webpack",
    "watch": "webpack --watch",
    "start": "webpack-dev-server"
  }

除了在git命令行里运行webpack,还可以在node中使用weback,在以前devServer还不成熟的时候,开发者是直接通过自己写node server来开启本地服务的,当在node里执行webpack的时候,devServer将直接被忽略,所以可以不删除
这里需要借助express 和 webpack-dev-middleware(中间件) 来实现,执行 npm i express webpack-dev-middleware -D安装这两个node模块
在package.json里创建一个server.js,scripts里添加一条server命令,使用node执行server.js

"scripts": {
    "bundle": "webpack",
    "watch": "webpack --watch",
    "start": "webpack-dev-server",
    "server": "node server.js"
}
/* server.js */
const express = require('express');
const webpack = require('webpack');
const webpackDevMiddleWare = require('webpack-dev-middleware');
const config = require('./webpack.config.js');
const complier = webpack(config); //webpack编译器,编译器执行一次代码就会打包一次

const app = express();

app.use(webpackDevMiddleWare(complier, {
    // 打包输出,因为我不在webpack.config.js里写publicPath,所以这里可以不写
    // publicPath: config.output.publicPath  
}));

app.listen(3000, () => {  // 本地服务器端口号为3000
    console.log('server is running');
})

执行npm run server,打开浏览器 localhost:3000,这个时候也能正常运行webpack打包后的文件,对到devServer里的很多东西,如果想实现还需要自己再添加很多模块,所以一般还是直接使用devServer来运行与配置本地服务

Section-2.9&2.10 Hot Module Replacement 热模块更新

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

const webpack = require('webpack');

devServer添加hot参数

devServer: {
    contentBase: './dist',
    open: true,
    proxy: {  // 接口代理
        '/api': 'http://localhost:80'
    },
    hot: true,     //webpack-dev-server开启热更新
    hotOnly: true  //html没生效,浏览器不刷新
},
plugins: [
    new HtmlWebpackPlugin({
        template: './index.html'
    }),
    new CleanWebpackPlugin(),
    new webpack.HotModuleReplacementPlugin() // 前面说了热更新是webpack的功能,所以需要作为插件加进来
]

这样在修改css的时候,页面不会重新刷新,只会重新渲染
但是对到js因为没有loader来帮我们做这些事,所以需要自己进行监听 模块热更新,通过accept来实现

if(module.hot) {
    module.hot.accept('./num', () => {
        // console.log(num)
        // num.reset()
        
        root.removeChild(num.block)
        num = new Num();
        root.appendChild(num.block)
    })
}
class StaticDiv {
    constructor () {
        this.num = 2000
        this.block = document.createElement('div')
        this.block.innerHTML = this.num
    }
    // reset () {
    //     this.block.innerHTML = this.num
    // }
}

export default StaticDiv

这里比较坑爹,个人尝试后发现,上面的注释是没用的,内存里的值还是原来那个,原本我并不想跟文档里一样将这个节点删掉重新加入,但是不行,重新innerHTML也是不行的,如我说的,内存没变,文件怎么修改num的值,index.js还是原来初始化页面的2000,无解,不知道vue底层怎么做的,技术不到位这里只做记录

Section-2.11&2.12 使用 Babel 处理 ES6 语法

当我们写es6的时候,由于需要兼容一些低版本浏览器,所以需要将es6转换为es5,这个时候我们需要使用 babel
跟着官网进行安装即可,进入setup(设置)进行查看
执行npm instal babel-loader @babel/core -D安装babel
实际上babel并不会对代码进行解析,官网上写的就是将代码直接输出而已,需要解析我们需要使用@babel/preset-env这个插件来解析,执行npm install @babel/preset-env -D 进行安装
单纯这样解析出来的ES5实际上一些低版本浏览器依然不识别,所以还需要使用 babel-polyfill 来进行进行解析
webpack.config.js里添加打包规则

{
    test: /\.js$/,
    exclude: /node_modules/,  // 如果代码是在node-modules,则排除
    loader: "babel-loader",   //webpack与babel的桥梁,并不会进行解析
    options: {
        presets: [['@babel/preset-env', {  // 解析es6语法
            useBuiltIns: 'usage',          // @babel/polyfill,用到的语法加入低版本解析,而不是全部加载进去
            corejs: 2,
            targets: {
                chrome: "67"  // 打包的目标是兼容chrome67以上,实际如果写这个babel不会解析,因为支持es6语法
            }
        }]]
    }
}

执行npm install --save @babel/polyfill进行安装
这里我尝试后如果不写corejs的版本,默认会以corejs2.x来(可以看编译时输出的提示),还需要注意,不能使用 --save-dev 去安装polyfill和corejs,因为他们是需要在文件头部引入的(因为这不是编译工具,他是增加拓展,往js里加代码的,我说的应该够直白易懂了吧)
还有,其实我写的是corejs: 3,因为我使用不写或写corejs: 2的时候,打包一直报没有各种模块,即使我删除重装还是一样,不确定是不是core-js版本问题

使用core2后一直提示没有对应模块,而我看了一下是有的

执行npm install --save @babel/runtime-corejs3,关于 corejs3的一些问题 可以参考这里,然而对到插件有些新增的我写了以后反而导致 useBuiltIns 无效了,代码打包后变得特别大,所以下面我全都注释掉了

package.json

/* webpack.config.js */
{
    test: /\.js$/,
    exclude: /node_modules/,
    loader: "babel-loader",
    options: {
        presets: [['@babel/preset-env', {
            useBuiltIns: 'usage',
            corejs: 3,  // 如果文件引入polyfill反而会出警告
            targets: {
                chrome: "30" // 故意写很低
            } 
        }]]
    }
}

/* index.js */
// import "@babel/polyfill";
// import "core-js/stable";
// import "regenerator-runtime/runtime";

const arr = [
    new Promise(() => {}),
    new Promise(() => {})
]

arr.map(item => {
    consele.log(item)
})

插件(plugin)和预设(preset)

代码转换功能以插件的形式出现,插件是小型的 JavaScript 程序,用于指导 Babel 如何对代码进行转换。你甚至可以编写自己的插件将你所需要的任何代码转换功能应用到你的代码上。例如将 ES2015+ 语法转换为 ES5 语法,我们可以使用诸如 @babel/plugin-transform-arrow-functions 之类的官方插件

Polyfill 是会污染全局环境的(添加全局范围(global scope)和类似 String 这样的内置原型(native prototypes)中),因此我们再项目工程中一般使用 presets,写第三方类库使用 plugin,通过 @babel/plugin-transform-runtime 来实现
需要三个插件

  1. npm install --save-dev @babel/plugin-transform-runtime 不会污染全局环境,会以闭包的形式去注入对应的内容
  2. npm install --save @babel/runtime
  3. npm install --save @babel/runtime-corejs2 如果corejs改成2,这里需要安装corejs2,这里看不懂,只知道一般需要改成2,没去查阅更多资料
    实际中我们可能会在babel写很多配置,因此更好的做法是建立 .babelrc 文件,将 options 挪到该文件中,具体查阅 bable中的配置 ,删除babel的options
// .babelrc
"plugins": [["@babel/plugin-transform-runtime",{
    "corejs": 2,
    "helps": true,
    "regenerator": true,
    "useESModules": false
}]]
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容

  • 版权声明:本文为博主原创文章,未经博主允许不得转载。 webpack介绍和使用 一、webpack介绍 1、由来 ...
    it筱竹阅读 11,060评论 0 21
  • 写在开头 先说说为什么要写这篇文章, 最初的原因是组里的小朋友们看了webpack文档后, 表情都是这样的: (摘...
    Lefter阅读 5,279评论 4 31
  • 目录第1章 webpack简介 11.1 webpack是什么? 11.2 官网地址 21.3 为什么使用 web...
    lemonzoey阅读 1,731评论 0 1
  • GitChat技术杂谈 前言 本文较长,为了节省你的阅读时间,在文前列写作思路如下: 什么是 webpack,它要...
    萧玄辞阅读 12,681评论 7 110
  • 慢慢听歌,慢慢喜欢一个人,慢慢读一本书,慢慢欣赏美景,慢慢...... 这人啊,就怕处。 忘了是哪部电视剧...
    R_江河阅读 533评论 0 4