Save CSS - Chrome extension makes you fly
My first Chrome extension Save CSS is now available in the Chrome web store!
In a typical workflow of web user interface development,
you write a little
view the result live in a browser where
continue tweaking the CSS with Developer tools in Chrome/Safari or with
Firebug in Firefox. When all the tweaks are finished, you then
modified CSS values manually one by one and paste them back into your editor,
save all files, and
finally view the results again in a browser to verify that
you remembered to copy everything. You repeat these steps a hundred times a
day. Clearly there is room for improvement.
This extension speeds up the workflow by
eliminating the manual copy paste
in Developer tools into the original source files in your local disk.
I have been using this extension privately since October 2011 when the experimental APIs became available in early Dev versions of Chrome. The API became officially supported this week when Chrome 18 was released and I was finally able to publish this extension in the Chrome store.
Fire up your Chrome browser and install the extension from the Chrome web store.
Then download the required Python script or Ruby script to your
computer, either one, it doesn't matter since they are identical in
functionality. Run the script from any folder:
python server.py or
server.rb. The script starts a small web server that actually writes the
changes sent by Chrome into the local disk since Chrome can't save files for
Of course, first make sure you have either Python or Ruby installed on your computer. The scripts do not require any additional libraries to install.
After installation, launch Developer Tools in Chrome. The extension user
interface is visible as the last tab,
Now change any CSS property in the Elements-tab, go to Save-tab and you will see an error that says no mapping found. The extension issues an error since it doesn't yet know where to save the changes to. You need to add a mapping rule for the site to map an URL to a local path.
Finally click Save and the mapping gets saved.
Now change a CSS again in the Elements-tab. If everything was configured correctly, the CSS was saved into local disk and you should see an entry in the "Saved Files" table. If not, you should see an error.
Here's a screenshot with one mapping and two successful saves:
To change a CSS property, double-click either on the property name or the
value and edit the value with a keyboard. Remember that arrow up and down can
change numeric values.
To add a new CSS property, double-click on the last line of a selector, the one
with "}", and start typing a new property name.
To delete a CSS property, double-click on the property name and delete the
whole name and press enter.
To add a new CSS selector, go to the Resources tab and select the desired CSS
file (remember the shortcut link in the Elements tab too). Double-click and
edit the CSS file as you wish. When you press CMD-S, the whole file is saved
and CSS becomes active.
just in time.
comments are preserved by Chrome. Even
vendor prefixes are properly
preserved. Chrome provides solid piece of tech.
The extension doesn't work with CSS compilation setups like LESS since the extension operates at the level of raw CSS and not at the higher level.
If you want to be careful with your CSS/JS tweakings, you can disable the automatic save functionality by unchecking the checkbox at the bottom of the "Save" tab. Then you can control when to save and what files to save. I'm not sure how useful this option is but it's there.
Under the hood
The extension relies on the following devtools API of Chrome:
This powerful API became officially supported in Chrome 18 in March 28, 2012.
Chrome extensions do not have access to the local file system, the write
operation needs to be done externally. The extension thus sends the content
to an external script via AJAX. Being a Python developer, I wrote the script
in Python. The script just receives the content and an absolute destination
file path, and writes the file to disk. In case of error, it returns a
The external script is simple since all the configurations are taken care of by the extension. Mappings are stored permanently into localstorage which is private to the extension.
The whole source code to the extension and to the Python and Ruby web server scripts are available in GitHub.
I'm a long-time Python developer but wrote the script also in Ruby for the fun of it. Here they are both side by side, like twin brothers.
Server scripts in Python and Ruby:
#!/usr/bin/python # -*- coding: utf-8 -*- # server.py: receive CSS and JS files from Chrome extension # and save files locally # # Author: Tomi.Mickelsson@iki.fi # 30.10.2011 - Created from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler class MyServer(BaseHTTPRequestHandler): def do_POST(self): hd = self.headers # chrome sent data: url = hd.get("X-origurl") fpath = hd.get("X-filepath") bodylen = int(hd['content-length']) body = self.rfile.read(bodylen) print url, " ->", fpath, len(body) reply = "OK" # save file try: f = open(fpath, "w") f.write(body) f.close() except Exception, e: print e reply = "Server couldn't save "+fpath # return reply self.send_response(200) self.end_headers() self.wfile.write(reply) # start http server server = HTTPServer(('localhost', 8080), MyServer) print "Server running in port 8080..." server.serve_forever()
#!/usr/bin/ruby # -*- coding: utf-8 -*- # server.rb: receive CSS and JS files from Chrome extension # and save files locally # # Author: Tomi.Mickelsson@iki.fi # 04.02.2012 - Created require 'webrick' include WEBrick class MyServlet < HTTPServlet::AbstractServlet def do_POST(req, res) url = req.header['x-origurl'] fpath = req.header['x-filepath'] bodylen = req.header['content-length'] raw = req.body fpath = fpath.join("") print url, " -> ", fpath, " ", bodylen, "\n" reply = "OK" # save file begin f = File.open(fpath, "wb") f.syswrite(raw) f.close rescue => e puts "EXCEP " + e.message reply = e.message end res.status = "200" res.body = reply end end # start server server = HTTPServer.new(:BindAddress => "localhost",:Port => 8080) server.mount('/', MyServlet) trap 'INT' do server.shutdown end puts "Server running in port 8080..." server.start
If you have issues with the extension, you can report them below or at GitHub.
Update: The Python script added extra line feeds on Windows. Fixed.comments powered by Disqus